blob: a60fb6bbe7a9231f8d4d49dcf80154a6006055ea [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for mb.py."""
import io
import json
import os
import re
import sys
import textwrap
import unittest
sys.path.insert(
0,
os.path.abspath(
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')))
from mb import mb
# Call has argument input to match subprocess.run
# pylint: disable=redefined-builtin
class FakeMBW(mb.MetaBuildWrapper):
def __init__(self, win32=False):
super().__init__()
# Override vars for test portability.
if win32:
self.chromium_src_dir = 'c:\\fake_src'
self.default_config = 'c:\\fake_src\\tools\\mb\\mb_config.pyl'
self.default_isolate_map = ('c:\\fake_src\\testing\\buildbot\\'
'gn_isolate_map.pyl')
self.temp = 'c:\\temp'
self.platform = 'win32'
self.executable = 'c:\\python\\python.exe'
self.sep = '\\'
self.cwd = 'c:\\fake_src\\out\\Default'
else:
self.chromium_src_dir = '/fake_src'
self.default_config = '/fake_src/tools/mb/mb_config.pyl'
self.default_isolate_map = '/fake_src/testing/buildbot/gn_isolate_map.pyl'
self.temp = '/tmp'
self.platform = 'linux'
self.executable = '/usr/bin/python'
self.sep = '/'
self.cwd = '/fake_src/out/Default'
self.files = {}
self.dirs = set()
self.calls = []
self.cmds = []
self.cross_compile = None
self.out = ''
self.err = ''
self.rmdirs = []
def Exists(self, path):
abs_path = self._AbsPath(path)
return (self.files.get(abs_path) is not None or abs_path in self.dirs)
def ListDir(self, path):
dir_contents = []
for f in list(self.files.keys()) + list(self.dirs):
head, _ = os.path.split(f)
if head == path:
dir_contents.append(f)
return dir_contents
def MaybeMakeDirectory(self, path):
abpath = self._AbsPath(path)
self.dirs.add(abpath)
def PathJoin(self, *comps):
return self.sep.join(comps)
def ReadFile(self, path):
try:
return self.files[self._AbsPath(path)]
except KeyError as e:
raise IOError('%s not found' % path) from e
def WriteFile(self, path, contents, force_verbose=False):
if self.args.dryrun or self.args.verbose or force_verbose:
self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
abpath = self._AbsPath(path)
self.files[abpath] = contents
def Call(self, cmd, env=None, capture_output=True, input=None):
# Avoid unused-argument warnings from Pylint
del env
del capture_output
del input
self.calls.append(cmd)
if self.cmds:
return self.cmds.pop(0)
return 0, '', ''
def Print(self, *args, **kwargs):
sep = kwargs.get('sep', ' ')
end = kwargs.get('end', '\n')
f = kwargs.get('file', sys.stdout)
if f == sys.stderr:
self.err += sep.join(args) + end
else:
self.out += sep.join(args) + end
def TempDir(self):
tmp_dir = self.temp + self.sep + 'mb_test'
self.dirs.add(tmp_dir)
return tmp_dir
def TempFile(self, mode='w'):
# Avoid unused-argument warnings from Pylint
del mode
return FakeFile(self.files)
def RemoveFile(self, path):
abpath = self._AbsPath(path)
self.files[abpath] = None
def RemoveDirectory(self, abs_path):
# Normalize the passed-in path to handle different working directories
# used during unit testing.
abs_path = self._AbsPath(abs_path)
self.rmdirs.append(abs_path)
files_to_delete = [f for f in self.files if f.startswith(abs_path)]
for f in files_to_delete:
self.files[f] = None
def _AbsPath(self, path):
if not ((self.platform == 'win32' and path.startswith('c:')) or
(self.platform != 'win32' and path.startswith('/'))):
path = self.PathJoin(self.cwd, path)
if self.sep == '\\':
return re.sub(r'\\+', r'\\', path)
return re.sub('/+', '/', path)
class FakeFile:
def __init__(self, files):
self.name = '/tmp/file'
self.buf = ''
self.files = files
def write(self, contents):
self.buf += contents
def close(self):
self.files[self.name] = self.buf
TEST_CONFIG = """\
{
'builder_groups': {
'chromium': {},
'fake_builder_group': {
'fake_args_bot': 'fake_args_bot',
'fake_args_file': 'args_file_goma',
'fake_builder': 'rel_bot',
'fake_debug_builder': 'debug_goma',
'fake_ios_error': 'ios_error',
'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'},
},
},
'configs': {
'args_file_goma': ['fake_args_bot', 'goma'],
'debug_goma': ['debug', 'goma'],
'fake_args_bot': ['fake_args_bot'],
'ios_error': ['error'],
'phase_1': ['rel', 'phase_1'],
'phase_2': ['rel', 'phase_2'],
'rel_bot': ['rel', 'goma', 'fake_feature1'],
},
'mixins': {
'debug': {
'gn_args': 'is_debug=true',
},
'error': {
'gn_args': 'error',
},
'fake_args_bot': {
'args_file': '//build/args/bots/fake_builder_group/fake_args_bot.gn',
},
'fake_feature1': {
'gn_args': 'enable_doom_melon=true',
},
'goma': {
'gn_args': 'use_goma=true',
},
'phase_1': {
'gn_args': 'phase=1',
},
'phase_2': {
'gn_args': 'phase=2',
},
'rel': {
'gn_args': 'is_debug=false dcheck_always_on=false',
},
},
}
"""
CONFIG_STARLARK_GN_ARGS = """\
{
'gn_args_locations_files': [
'../../infra/config/generated/builders/gn_args_locations.json',
],
'builder_groups': {
},
'configs': {
},
'mixins': {
},
}
"""
TEST_GN_ARGS_LOCATIONS_JSON = """\
{
"chromium": {
"linux-official": "ci/linux-official/gn-args.json"
},
"tryserver.chromium": {
"linux-official": "try/linux-official/gn-args.json"
}
}
"""
TEST_GN_ARGS_JSON = """\
{
"gn_args": {
"string_arg": "has double quotes",
"bool_arg_lower_case": true
}
}
"""
TEST_PHASED_GN_ARGS_JSON = """\
{
"phases": {
"phase_1": {
"gn_args": {
"string_arg": "has double quotes",
"bool_arg_lower_case": true
}
},
"phase_2": {
"gn_args": {
"string_arg": "second phase",
"bool_arg_lower_case": false
}
}
}
}
"""
TEST_BAD_CONFIG = """\
{
'configs': {
'rel_bot_1': ['rel', 'chrome_with_codecs'],
'rel_bot_2': ['rel', 'bad_nested_config'],
},
'builder_groups': {
'chromium': {
'a': 'rel_bot_1',
'b': 'rel_bot_2',
},
},
'mixins': {
'chrome_with_codecs': {
'gn_args': 'proprietary_codecs=true',
},
'bad_nested_config': {
'mixins': ['chrome_with_codecs'],
},
'rel': {
'gn_args': 'is_debug=false',
},
},
}
"""
TEST_ARGS_FILE_TWICE_CONFIG = """\
{
'builder_groups': {
'chromium': {},
'fake_builder_group': {
'fake_args_file_twice': 'args_file_twice',
},
},
'configs': {
'args_file_twice': ['args_file', 'args_file'],
},
'mixins': {
'args_file': {
'args_file': '//build/args/fake.gn',
},
},
}
"""
TEST_DUP_CONFIG = """\
{
'builder_groups': {
'chromium': {},
'fake_builder_group': {
'fake_builder': 'some_config',
'other_builder': 'some_other_config',
},
},
'configs': {
'some_config': ['args_file'],
'some_other_config': ['args_file'],
},
'mixins': {
'args_file': {
'args_file': '//build/args/fake.gn',
},
},
}
"""
TRYSERVER_CONFIG = """\
{
'builder_groups': {
'not_a_tryserver': {
'fake_builder': 'fake_config',
},
'tryserver.chromium.linux': {
'try_builder': 'fake_config',
},
'tryserver.chromium.mac': {
'try_builder2': 'fake_config',
},
},
'configs': {},
'mixins': {},
}
"""
def is_win():
return sys.platform == 'win32'
class UnitTest(unittest.TestCase):
maxDiff = None
def fake_mbw(self, files=None, win32=False):
mbw = FakeMBW(win32=win32)
mbw.files.setdefault(mbw.default_config, TEST_CONFIG)
mbw.files.setdefault(
mbw.ToAbsPath('//testing/buildbot/gn_isolate_map.pyl'),
'''{
"foo_unittests": {
"label": "//foo:foo_unittests",
"type": "console_test_launcher",
"args": [],
},
}''')
mbw.files.setdefault(
mbw.ToAbsPath('//build/args/bots/fake_builder_group/fake_args_bot.gn'),
'is_debug = false\ndcheck_always_on=false\n')
mbw.files.setdefault(mbw.ToAbsPath('//tools/mb/rts_banned_suites.json'),
'{}')
if files:
for path, contents in files.items():
mbw.files[path] = contents
return mbw
def check(self, args, mbw=None, files=None, out=None, err=None, ret=None,
env=None):
if not mbw:
mbw = self.fake_mbw(files)
try:
prev_env = os.environ.copy()
if env:
os.environ.clear()
os.environ.update(env)
actual_ret = mbw.Main(args)
finally:
os.environ.clear()
os.environ.update(prev_env)
self.assertEqual(
actual_ret, ret,
"ret: %s, out: %s, err: %s" % (actual_ret, mbw.out, mbw.err))
if out is not None:
self.assertEqual(mbw.out, out)
if err is not None:
self.assertEqual(mbw.err, err)
return mbw
def path(self, p):
if is_win():
return 'c:' + p.replace('/', '\\')
return p
def test_analyze(self):
files = {'/tmp/in.json': '''{\
"files": ["foo/foo_unittest.cc"],
"test_targets": ["foo_unittests"],
"additional_compile_targets": ["all"]
}''',
'/tmp/out.json.gn': '''{\
"status": "Found dependency",
"compile_targets": ["//foo:foo_unittests"],
"test_targets": ["//foo:foo_unittests"]
}'''}
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
self.assertEqual(out, {
'status': 'Found dependency',
'compile_targets': ['foo:foo_unittests'],
'test_targets': ['foo_unittests']
})
def test_analyze_optimizes_compile_for_all(self):
files = {'/tmp/in.json': '''{\
"files": ["foo/foo_unittest.cc"],
"test_targets": ["foo_unittests"],
"additional_compile_targets": ["all"]
}''',
'/tmp/out.json.gn': '''{\
"status": "Found dependency",
"compile_targets": ["//foo:foo_unittests", "all"],
"test_targets": ["//foo:foo_unittests"]
}'''}
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
# check that 'foo_unittests' is not in the compile_targets
self.assertEqual(['all'], out['compile_targets'])
def test_analyze_handles_other_toolchains(self):
files = {'/tmp/in.json': '''{\
"files": ["foo/foo_unittest.cc"],
"test_targets": ["foo_unittests"],
"additional_compile_targets": ["all"]
}''',
'/tmp/out.json.gn': '''{\
"status": "Found dependency",
"compile_targets": ["//foo:foo_unittests",
"//foo:foo_unittests(bar)"],
"test_targets": ["//foo:foo_unittests"]
}'''}
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
# crbug.com/736215: If GN returns a label containing a toolchain,
# MB (and Ninja) don't know how to handle it; to work around this,
# we give up and just build everything we were asked to build. The
# output compile_targets should include all of the input test_targets and
# additional_compile_targets.
self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
def test_analyze_handles_way_too_many_results(self):
too_many_files = ', '.join(['"//foo:foo%d"' % i for i in range(40 * 1024)])
files = {'/tmp/in.json': '''{\
"files": ["foo/foo_unittest.cc"],
"test_targets": ["foo_unittests"],
"additional_compile_targets": ["all"]
}''',
'/tmp/out.json.gn': '''{\
"status": "Found dependency",
"compile_targets": [''' + too_many_files + '''],
"test_targets": ["//foo:foo_unittests"]
}'''}
mbw = self.fake_mbw(files)
mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
self.check(['analyze', '-c', 'debug_goma', '//out/Default',
'/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
out = json.loads(mbw.files['/tmp/out.json'])
# If GN returns so many compile targets that we might have command-line
# issues, we should give up and just build everything we were asked to
# build. The output compile_targets should include all of the input
# test_targets and additional_compile_targets.
self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
def test_gen(self):
mbw = self.fake_mbw()
self.check(['gen', '-c', 'debug_goma', '//out/Default', '-g', '/goma'],
mbw=mbw, ret=0)
self.assertMultiLineEqual(mbw.files['/fake_src/out/Default/args.gn'],
('goma_dir = "/goma"\n'
'is_debug = true\n'
'use_goma = true\n'))
# Make sure we log both what is written to args.gn and the command line.
self.assertIn('Writing """', mbw.out)
self.assertIn('/fake_src/buildtools/linux64/gn gen //out/Default --check',
mbw.out)
mbw = self.fake_mbw(win32=True)
self.check(['gen', '-c', 'debug_goma', '-g', 'c:\\goma', '//out/Debug'],
mbw=mbw, ret=0)
self.assertMultiLineEqual(mbw.files['c:\\fake_src\\out\\Debug\\args.gn'],
('goma_dir = "c:\\\\goma"\n'
'is_debug = true\n'
'use_goma = true\n'))
self.assertIn(
'c:\\fake_src\\buildtools\\win\\gn.exe gen //out/Debug '
'--check', mbw.out)
mbw = self.fake_mbw()
self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_bot',
'//out/Debug'],
mbw=mbw, ret=0)
# TODO(https://crbug.com/1093038): This assert is inappropriately failing.
# self.assertEqual(
# mbw.files['/fake_src/out/Debug/args.gn'],
# 'import("//build/args/bots/fake_builder_group/fake_args_bot.gn")\n')
def test_gen_args_file_mixins(self):
mbw = self.fake_mbw()
self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_file',
'//out/Debug'], mbw=mbw, ret=0)
self.assertEqual(
mbw.files['/fake_src/out/Debug/args.gn'],
('import("//build/args/bots/fake_builder_group/fake_args_bot.gn")\n'
'use_goma = true\n'))
def test_gen_args_file_twice(self):
mbw = self.fake_mbw()
mbw.files[mbw.default_config] = TEST_ARGS_FILE_TWICE_CONFIG
self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_file_twice',
'//out/Debug'], mbw=mbw, ret=1)
def test_gen_fails(self):
mbw = self.fake_mbw()
mbw.Call = lambda cmd, env=None, capture_output=True, input='': (1, '', '')
self.check(['gen', '-c', 'debug_goma', '//out/Default'], mbw=mbw, ret=1)
def test_gen_swarming(self):
files = {
'/tmp/swarming_targets':
'base_unittests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
}
mbw = self.fake_mbw(files)
def fake_call(cmd, env=None, capture_output=True, input=''):
del cmd
del env
del capture_output
del input
mbw.files['/fake_src/out/Default/base_unittests.runtime_deps'] = (
'base_unittests\n')
return 0, '', ''
mbw.Call = fake_call
self.check(['gen',
'-c', 'debug_goma',
'--swarming-targets-file', '/tmp/swarming_targets',
'//out/Default'], mbw=mbw, ret=0)
self.assertIn('/fake_src/out/Default/base_unittests.isolate',
mbw.files)
self.assertIn('/fake_src/out/Default/base_unittests.isolated.gen.json',
mbw.files)
def test_gen_swarming_script(self):
files = {
'/tmp/swarming_targets': 'cc_perftests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl': (
"{'cc_perftests': {"
" 'label': '//cc:cc_perftests',"
" 'type': 'script',"
" 'script': '/fake_src/out/Default/test_script.py',"
"}}\n"
),
}
mbw = self.fake_mbw(files=files)
def fake_call(cmd, env=None, capture_output=True, input=''):
del cmd
del env
del capture_output
del input
mbw.files['/fake_src/out/Default/cc_perftests.runtime_deps'] = (
'cc_perftests\n')
return 0, '', ''
mbw.Call = fake_call
self.check(['gen',
'-c', 'debug_goma',
'--swarming-targets-file', '/tmp/swarming_targets',
'--isolate-map-file',
'/fake_src/testing/buildbot/gn_isolate_map.pyl',
'//out/Default'], mbw=mbw, ret=0)
self.assertIn('/fake_src/out/Default/cc_perftests.isolate',
mbw.files)
self.assertIn('/fake_src/out/Default/cc_perftests.isolated.gen.json',
mbw.files)
def test_multiple_isolate_maps(self):
files = {
'/tmp/swarming_targets':
'cc_perftests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'cc_perftests': {"
" 'label': '//cc:cc_perftests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'/fake_src/testing/buildbot/gn_isolate_map2.pyl':
("{'cc_perftests2': {"
" 'label': '//cc:cc_perftests',"
" 'type': 'console_test_launcher',"
"}}\n"),
}
mbw = self.fake_mbw(files=files)
def fake_call(cmd, env=None, capture_output=True, input=''):
del cmd
del env
del capture_output
del input
mbw.files['/fake_src/out/Default/cc_perftests.runtime_deps'] = (
'cc_perftests_fuzzer\n')
return 0, '', ''
mbw.Call = fake_call
self.check(['gen',
'-c', 'debug_goma',
'--swarming-targets-file', '/tmp/swarming_targets',
'--isolate-map-file',
'/fake_src/testing/buildbot/gn_isolate_map.pyl',
'--isolate-map-file',
'/fake_src/testing/buildbot/gn_isolate_map2.pyl',
'//out/Default'], mbw=mbw, ret=0)
self.assertIn('/fake_src/out/Default/cc_perftests.isolate',
mbw.files)
self.assertIn('/fake_src/out/Default/cc_perftests.isolated.gen.json',
mbw.files)
def test_duplicate_isolate_maps(self):
files = {
'/tmp/swarming_targets':
'cc_perftests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'cc_perftests': {"
" 'label': '//cc:cc_perftests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'/fake_src/testing/buildbot/gn_isolate_map2.pyl':
("{'cc_perftests': {"
" 'label': '//cc:cc_perftests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps':
("cc_perftests\n"),
}
mbw = self.fake_mbw(files=files, win32=True)
# Check that passing duplicate targets into mb fails.
self.check(['gen',
'-c', 'debug_goma',
'--swarming-targets-file', '/tmp/swarming_targets',
'--isolate-map-file',
'/fake_src/testing/buildbot/gn_isolate_map.pyl',
'--isolate-map-file',
'/fake_src/testing/buildbot/gn_isolate_map2.pyl',
'//out/Default'], mbw=mbw, ret=1)
def test_isolate(self):
files = {
'/fake_src/out/Default/toolchain.ninja':
"",
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'/fake_src/out/Default/base_unittests.runtime_deps':
("base_unittests\n"),
}
self.check(['isolate', '-c', 'debug_goma', '//out/Default',
'base_unittests'], files=files, ret=0)
# test running isolate on an existing build_dir
files['/fake_src/out/Default/args.gn'] = 'is_debug = true\n'
self.check(['isolate', '//out/Default', 'base_unittests'],
files=files, ret=0)
self.check(['isolate', '//out/Default', 'base_unittests'],
files=files, ret=0)
# Existing build dir that uses a .gni import.
files['/fake_src/out/Default/args.gn'] = 'import("//import/args.gni")\n'
files['/fake_src/import/args.gni'] = 'is_debug = true\n'
self.check(['isolate', '//out/Default', 'base_unittests'],
files=files,
ret=0)
def test_dedup_runtime_deps(self):
files = {
'/tmp/swarming_targets':
'base_unittests\n',
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
}
mbw = self.fake_mbw(files)
def fake_call(cmd, env=None, capture_output=True, input=''):
del cmd
del env
del capture_output
del input
mbw.files['/fake_src/out/Default/base_unittests.runtime_deps'] = (
'base_unittests\n'
'../../filters/some_filter/\n'
'../../filters/some_filter/foo\n'
'../../filters/another_filter/hoo\n')
return 0, '', ''
mbw.Call = fake_call
self.check([
'gen', '-c', 'debug_goma', '--swarming-targets-file',
'/tmp/swarming_targets', '//out/Default'
],
mbw=mbw,
ret=0)
self.assertIn('/fake_src/out/Default/base_unittests.isolate', mbw.files)
files = mbw.files.get('/fake_src/out/Default/base_unittests.isolate')
self.assertIn('../../filters/some_filter', files)
self.assertNotIn('../../filters/some_filter/foo', files)
self.assertIn('../../filters/another_filter/hoo', files)
def test_isolate_dir(self):
files = {
'/fake_src/out/Default/toolchain.ninja':
"",
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
}
mbw = self.fake_mbw(files=files)
mbw.cmds.append((0, '', '')) # Result of `gn gen`
mbw.cmds.append((0, '', '')) # Result of `autoninja`
# Result of `gn desc runtime_deps`
mbw.cmds.append((0, 'base_unitests\n../../test_data/\n', ''))
self.check(['isolate', '-c', 'debug_goma', '//out/Default',
'base_unittests'], mbw=mbw, ret=0, err='')
def test_isolate_generated_dir(self):
files = {
'/fake_src/out/Default/toolchain.ninja':
"",
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
}
mbw = self.fake_mbw(files=files)
mbw.cmds.append((0, '', '')) # Result of `gn gen`
mbw.cmds.append((0, '', '')) # Result of `autoninja`
# Result of `gn desc runtime_deps`
mbw.cmds.append((0, 'base_unitests\ntest_data/\n', ''))
expected_err = ('error: gn `data` items may not list generated directories;'
' list files in directory instead for:\n'
'//out/Default/test_data/\n')
self.check(['isolate', '-c', 'debug_goma', '//out/Default',
'base_unittests'], mbw=mbw, ret=1)
self.assertEqual(mbw.out[-len(expected_err):], expected_err)
def test_run(self):
files = {
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'/fake_src/out/Default/base_unittests.runtime_deps':
("base_unittests\n"),
}
mbw = self.check(['run', '-c', 'debug_goma', '//out/Default',
'base_unittests'], files=files, ret=0)
# pylint: disable=line-too-long
self.assertEqual(
mbw.files['/fake_src/out/Default/base_unittests.isolate'],
'{"variables": {"command": ["vpython3", "../../testing/test_env.py", "./base_unittests", "--test-launcher-bot-mode", "--asan=0", "--lsan=0", "--msan=0", "--tsan=0", "--cfi-diag=0"], "files": ["../../.vpython3", "../../testing/test_env.py"]}}\n')
# pylint: enable=line-too-long
def test_run_swarmed(self):
files = {
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'/fake_src/out/Default/base_unittests.runtime_deps':
("base_unittests\n"),
'/fake_src/out/Default/base_unittests.archive.json':
("{\"base_unittests\":\"fake_hash\"}"),
'/fake_src/third_party/depot_tools/cipd_manifest.txt':
("# vpython\n"
"/some/vpython/pkg git_revision:deadbeef\n"),
}
task_json = json.dumps({'tasks': [{'task_id': '00000'}]})
collect_json = json.dumps({'00000': {'results': {}}})
mbw = self.fake_mbw(files=files)
mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
original_impl = mbw.ToSrcRelPath
def to_src_rel_path_stub(path):
if path.endswith('base_unittests.archive.json'):
return 'base_unittests.archive.json'
return original_impl(path)
mbw.ToSrcRelPath = to_src_rel_path_stub
self.check(['run', '-s', '-c', 'debug_goma', '//out/Default',
'base_unittests'], mbw=mbw, ret=0)
# Specify a custom dimension via '-d'.
mbw = self.fake_mbw(files=files)
mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
mbw.ToSrcRelPath = to_src_rel_path_stub
self.check(['run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7',
'//out/Default', 'base_unittests'], mbw=mbw, ret=0)
# Use the internal swarming server via '--internal'.
mbw = self.fake_mbw(files=files)
mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
mbw.ToSrcRelPath = to_src_rel_path_stub
self.check([
'run', '-s', '--internal', '-c', 'debug_goma', '//out/Default',
'base_unittests'
],
mbw=mbw,
ret=0)
def test_run_swarmed_task_failure(self):
files = {
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
("{'base_unittests': {"
" 'label': '//base:base_unittests',"
" 'type': 'console_test_launcher',"
"}}\n"),
'/fake_src/out/Default/base_unittests.runtime_deps':
("base_unittests\n"),
'/fake_src/out/Default/base_unittests.archive.json':
("{\"base_unittests\":\"fake_hash\"}"),
'/fake_src/third_party/depot_tools/cipd_manifest.txt':
("# vpython\n"
"/some/vpython/pkg git_revision:deadbeef\n"),
}
task_json = json.dumps({'tasks': [{'task_id': '00000'}]})
collect_json = json.dumps({'00000': {'results': {'exit_code': 1}}})
mbw = self.fake_mbw(files=files)
mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
original_impl = mbw.ToSrcRelPath
def to_src_rel_path_stub(path):
if path.endswith('base_unittests.archive.json'):
return 'base_unittests.archive.json'
return original_impl(path)
mbw.ToSrcRelPath = to_src_rel_path_stub
self.check(
['run', '-s', '-c', 'debug_goma', '//out/Default', 'base_unittests'],
mbw=mbw,
ret=1)
mbw = self.fake_mbw(files=files)
mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
mbw.ToSrcRelPath = to_src_rel_path_stub
self.check([
'run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7', '//out/Default',
'base_unittests'
],
mbw=mbw,
ret=1)
def test_lookup(self):
self.check(['lookup', '-c', 'debug_goma'], ret=0,
out=('\n'
'Writing """\\\n'
'is_debug = true\n'
'use_goma = true\n'
'""" to _path_/args.gn.\n\n'
'/fake_src/buildtools/linux64/gn gen _path_\n'))
def gen_starlark_gn_args_mbw(self, gn_args_json):
files = {
self.path('/fake_src/tools/mb/mb_config.pyl'):
CONFIG_STARLARK_GN_ARGS,
self.path('/fake_src/tools/mb/../../infra/config/generated/builders/'
'gn_args_locations.json'):
TEST_GN_ARGS_LOCATIONS_JSON,
self.path('/fake_src/tools/mb/../../infra/config/generated/builders/'
'ci/linux-official/gn-args.json'):
gn_args_json,
}
return self.fake_mbw(files=files, win32=is_win())
def test_lookup_starlark_gn_args(self):
mbw = self.gen_starlark_gn_args_mbw(TEST_GN_ARGS_JSON)
expected_out = ('\n'
'Writing """\\\n'
'bool_arg_lower_case = true\n'
'string_arg = "has double quotes"\n'
'""" to _path_/args.gn.\n\n')
if sys.platform == 'win32':
expected_out += 'c:\\fake_src\\buildtools\\win\\gn.exe gen _path_\n'
else:
expected_out += '/fake_src/buildtools/linux64/gn gen _path_\n'
self.check(['lookup', '-m', 'chromium', '-b', 'linux-official'],
mbw=mbw,
ret=0,
out=expected_out)
def test_lookup_starlark_gn_args_specified_phase(self):
mbw = self.gen_starlark_gn_args_mbw(TEST_GN_ARGS_JSON)
self.check([
'lookup', '-m', 'chromium', '-b', 'linux-official', '--phase', 'phase_1'
],
mbw=mbw,
ret=1)
self.assertIn(
'MBErr: Must not specify a build --phase '
'for linux-official on chromium', mbw.out)
def test_lookup_starlark_phased_gn_args(self):
mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
expected_out = ('\n'
'Writing """\\\n'
'bool_arg_lower_case = false\n'
'string_arg = "second phase"\n'
'""" to _path_/args.gn.\n\n')
if sys.platform == 'win32':
expected_out += 'c:\\fake_src\\buildtools\\win\\gn.exe gen _path_\n'
else:
expected_out += '/fake_src/buildtools/linux64/gn gen _path_\n'
self.check([
'lookup', '-m', 'chromium', '-b', 'linux-official', '--phase', 'phase_2'
],
mbw=mbw,
ret=0,
out=expected_out)
def test_lookup_starlark_phased_gn_args_no_phase(self):
mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
self.check(['lookup', '-m', 'chromium', '-b', 'linux-official'],
mbw=mbw,
ret=1)
self.assertIn(
'MBErr: Must specify a build --phase for linux-official on chromium',
mbw.out)
def test_lookup_starlark_phased_gn_args_wrong_phase(self):
mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
self.check([
'lookup', '-m', 'chromium', '-b', 'linux-official', '--phase', 'phase_3'
],
mbw=mbw,
ret=1)
self.assertIn(
'MBErr: Phase phase_3 doesn\'t exist for linux-official on chromium',
mbw.out)
def test_lookup_gn_args_with_non_existent_gn_args_location_file(self):
files = {
self.path('/fake_src/tools/mb/mb_config.pyl'):
textwrap.dedent("""\
{
'gn_args_locations_files': [
'../../infra/config/generated/builders/gn_args_locations.json',
],
'builder_groups': {
'fake-group': {
'fake-builder': 'fake-config',
},
},
'configs': {
'fake-config': [],
},
'mixins': {},
}
""")
}
mbw = self.fake_mbw(files=files, win32=is_win())
self.check(['lookup', '-m', 'fake-group', '-b', 'fake-builder'],
mbw=mbw,
ret=0)
def test_quiet_lookup(self):
self.check(['lookup', '-c', 'debug_goma', '--quiet'], ret=0,
out=('is_debug = true\n'
'use_goma = true\n'))
def test_lookup_goma_dir_expansion(self):
self.check(['lookup', '-c', 'rel_bot', '-g', '/foo'],
ret=0,
out=('\n'
'Writing """\\\n'
'dcheck_always_on = false\n'
'enable_doom_melon = true\n'
'goma_dir = "/foo"\n'
'is_debug = false\n'
'use_goma = true\n'
'""" to _path_/args.gn.\n\n'
'/fake_src/buildtools/linux64/gn gen _path_\n'))
def test_help(self):
orig_stdout = sys.stdout
try:
sys.stdout = io.StringIO()
self.assertRaises(SystemExit, self.check, ['-h'])
self.assertRaises(SystemExit, self.check, ['help'])
self.assertRaises(SystemExit, self.check, ['help', 'gen'])
finally:
sys.stdout = orig_stdout
def test_multiple_phases(self):
# Check that not passing a --phase to a multi-phase builder fails.
mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
'fake_multi_phase'], ret=1)
self.assertIn('Must specify a build --phase', mbw.out)
# Check that passing a --phase to a single-phase builder fails.
mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
'fake_builder', '--phase', 'phase_1'], ret=1)
self.assertIn('Must not specify a build --phase', mbw.out)
# Check that passing a wrong phase key to a multi-phase builder fails.
mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
'fake_multi_phase', '--phase', 'wrong_phase'], ret=1)
self.assertIn('Phase wrong_phase doesn\'t exist', mbw.out)
# Check that passing a correct phase key to a multi-phase builder passes.
mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
'fake_multi_phase', '--phase', 'phase_1'], ret=0)
self.assertIn('phase = 1', mbw.out)
mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
'fake_multi_phase', '--phase', 'phase_2'], ret=0)
self.assertIn('phase = 2', mbw.out)
def test_recursive_lookup(self):
files = {
'/fake_src/build/args/fake.gn': (
'enable_doom_melon = true\n'
'enable_antidoom_banana = true\n'
)
}
self.check([
'lookup', '-m', 'fake_builder_group', '-b', 'fake_args_file',
'--recursive'
],
files=files,
ret=0,
out=('dcheck_always_on = false\n'
'is_debug = false\n'
'use_goma = true\n'))
def test_train(self):
mbw = self.fake_mbw()
temp_dir = mbw.TempDir()
self.check(['train', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
self.assertIn(os.path.join(temp_dir, 'fake_builder_group.json'), mbw.files)
def test_validate(self):
mbw = self.fake_mbw()
self.check(['validate'], mbw=mbw, ret=0)
def test_bad_validate(self):
mbw = self.fake_mbw()
mbw.files[mbw.default_config] = TEST_BAD_CONFIG
self.check(['validate', '-f', mbw.default_config], mbw=mbw, ret=1)
def test_duplicate_validate(self):
mbw = self.fake_mbw()
mbw.files[mbw.default_config] = TEST_DUP_CONFIG
self.check(['validate'], mbw=mbw, ret=1)
self.assertIn(
'Duplicate configs detected. When evaluated fully, the '
'following configs are all equivalent: \'some_config\', '
'\'some_other_config\'.', mbw.out)
def test_good_expectations_validate(self):
mbw = self.fake_mbw()
# Train the expectations normally.
temp_dir = mbw.TempDir()
self.check(['train', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
# Immediately validating them should pass.
self.check(['validate', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
def test_bad_expectations_validate(self):
mbw = self.fake_mbw()
# Train the expectations normally.
temp_dir = mbw.TempDir()
self.check(['train', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
# Remove one of the expectation files.
mbw.files.pop(os.path.join(temp_dir, 'fake_builder_group.json'))
# Now validating should fail.
self.check(['validate', '--expectations-dir', temp_dir], mbw=mbw, ret=1)
self.assertIn('Expectations out of date', mbw.out)
def test_build_command_unix(self):
files = {
'/fake_src/out/Default/toolchain.ninja':
'',
'/fake_src/testing/buildbot/gn_isolate_map.pyl':
('{"base_unittests": {'
' "label": "//base:base_unittests",'
' "type": "console_test_launcher",'
' "args": [],'
'}}\n')
}
mbw = self.fake_mbw(files)
self.check(['run', '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
self.assertIn(['autoninja', '-C', 'out/Default', 'base_unittests'],
mbw.calls)
def test_build_command_windows(self):
files = {
'c:\\fake_src\\out\\Default\\toolchain.ninja':
'',
'c:\\fake_src\\testing\\buildbot\\gn_isolate_map.pyl':
('{"base_unittests": {'
' "label": "//base:base_unittests",'
' "type": "console_test_launcher",'
' "args": [],'
'}}\n')
}
mbw = self.fake_mbw(files, True)
self.check(['run', '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
self.assertIn(['autoninja.bat', '-C', 'out\\Default', 'base_unittests'],
mbw.calls)
def test_ios_error_config_with_ios_json(self):
"""Ensures that ios_error config finds the correct iOS JSON file for args"""
files = {
'/fake_src/ios/build/bots/fake_builder_group/fake_ios_error.json':
('{"gn_args": ["is_debug=true"]}\n')
}
mbw = self.fake_mbw(files)
self.check(['lookup', '-m', 'fake_builder_group', '-b', 'fake_ios_error'],
mbw=mbw,
ret=0,
out=('\n'
'Writing """\\\n'
'is_debug = true\n'
'""" to _path_/args.gn.\n\n'
'/fake_src/buildtools/linux64/gn gen _path_\n'))
def test_bot_definition_in_ios_json_only(self):
"""Ensures that logic checks iOS JSON file for args
When builder definition is not present, ensure that ios/build/bots/ is
checked.
"""
files = {
'/fake_src/ios/build/bots/fake_builder_group/fake_ios_bot.json':
('{"gn_args": ["is_debug=true"]}\n')
}
mbw = self.fake_mbw(files)
self.check(['lookup', '-m', 'fake_builder_group', '-b', 'fake_ios_bot'],
mbw=mbw,
ret=0,
out=('\n'
'Writing """\\\n'
'is_debug = true\n'
'""" to _path_/args.gn.\n\n'
'/fake_src/buildtools/linux64/gn gen _path_\n'))
def test_ios_error_config_missing_json_definition(self):
"""Ensures MBErr is thrown
Expect MBErr with 'No iOS definition ...' for iOS bots when the bot config
is ios_error, but there is no iOS JSON definition for it.
"""
mbw = self.fake_mbw()
self.check(['lookup', '-m', 'fake_builder_group', '-b', 'fake_ios_error'],
mbw=mbw,
ret=1)
self.assertIn('MBErr: No iOS definition was found.', mbw.out)
def test_bot_missing_definition(self):
"""Ensures builder missing MBErr is thrown
Expect the original MBErr to be thrown for iOS bots when the bot definition
doesn't exist at all.
"""
mbw = self.fake_mbw()
self.check(['lookup', '-m', 'fake_builder_group', '-b', 'random_bot'],
mbw=mbw,
ret=1)
self.assertIn('MBErr: Builder name "random_bot" not found under groups',
mbw.out)
if __name__ == '__main__':
unittest.main()