0
Files
src/tools/mb/mb_unittest.py
Ben Pastene 11d8b76601 Add deprecation notices to test reproduction tools
With the development and use of the UTR as far long as it is, these
old tools have mostly been obviated. So this adds a deprecation
notice to run-swarmed.py as well as "mb.py run". This notice makes the
scripts fail, unless --force is passed in. Plan is to let this sit
for a while, then eventually delete the scripts.

Bug: 386167803
Change-Id: I40a663a1ce4ea6d64d75a8007d14024bf9319d53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6127280
Reviewed-by: Brian Sheedy <bsheedy@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@google.com>
Commit-Queue: Ben Pastene <bpastene@chromium.org>
Reviewed-by: Struan Shrimpton <sshrimp@google.com>
Cr-Commit-Position: refs/heads/main@{#1425877}
2025-02-27 12:00:49 -08:00

1346 lines
41 KiB
Python
Executable File

#!/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_remoteexec',
'fake_builder': 'rel_bot',
'fake_debug_builder': 'debug_remoteexec',
'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'},
},
},
'configs': {
'args_file_remoteexec': ['fake_args_bot', 'remoteexec'],
'debug_remoteexec': ['debug', 'remoteexec'],
'fake_args_bot': ['fake_args_bot'],
'phase_1': ['rel', 'phase_1'],
'phase_2': ['rel', 'phase_2'],
'rel_bot': ['rel', 'remoteexec', 'fake_feature1'],
},
'mixins': {
'debug': {
'gn_args': 'is_debug=true',
},
'fake_args_bot': {
'args_file': '//build/args/bots/fake_builder_group/fake_args_bot.gn',
},
'fake_feature1': {
'gn_args': 'enable_doom_melon=true',
},
'phase_1': {
'gn_args': 'phase=1',
},
'phase_2': {
'gn_args': 'phase=2',
},
'rel': {
'gn_args': 'is_debug=false dcheck_always_on=false',
},
'remoteexec': {
'gn_args': 'use_remoteexec=true',
},
},
}
"""
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,
"string_list_arg": ["foo", "bar", "baz"],
"dict_arg": {
"string": "foo",
"bool": true,
"list": ["foo", "bar", "baz"]
}
}
}
"""
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):
"""Unit tests for mb.py."""
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)
prev_env = os.environ.copy()
try:
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_remoteexec', '//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_remoteexec', '//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_remoteexec', '//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_remoteexec', '//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_remoteexec', '//out/Default'],
mbw=mbw,
ret=0)
self.assertMultiLineEqual(mbw.files['/fake_src/out/Default/args.gn'],
('is_debug = true\n'
'use_remoteexec = 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_remoteexec', '//out/Debug'], mbw=mbw, ret=0)
self.assertMultiLineEqual(mbw.files['c:\\fake_src\\out\\Debug\\args.gn'],
('is_debug = true\n'
'use_remoteexec = 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: crbug.com/40134852 - 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_remoteexec = 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_remoteexec', '//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_remoteexec', '--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_remoteexec', '--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_remoteexec', '--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"),
r'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_remoteexec', '--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_remoteexec', '//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_remoteexec', '--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_gen_isolate_generated_dir(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'] = (
'test_data/\n')
return 0, '', ''
mbw.Call = fake_call
self.check([
'gen', '-c', 'debug_remoteexec', '--swarming-targets-file',
'/tmp/swarming_targets', '//out/Default'
],
mbw=mbw,
ret=1)
expected_err = ('error: gn `data` items may not list generated directories;'
' list files in directory instead for:\n'
'//out/Default/test_data/\n')
self.assertIn(expected_err, mbw.out)
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_remoteexec', '//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_remoteexec', '//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_remoteexec',
'//out/Default',
'base_unittests',
'--force',
],
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
# Check to make sure we're including the relative cwd and the
# command line in the call to `isolate`.
self.assertIn(
'relative-cwd out/Default -- vpython3 '
'../../testing/test_env.py', mbw.out)
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_remoteexec',
'//out/Default',
'base_unittests',
'--force',
],
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_remoteexec',
'-d',
'os',
'Win7',
'//out/Default',
'base_unittests',
'--force',
],
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_remoteexec',
'//out/Default',
'base_unittests',
'--force',
],
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_remoteexec',
'//out/Default',
'base_unittests',
'--force',
],
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_remoteexec',
'-d',
'os',
'Win7',
'//out/Default',
'base_unittests',
'--force',
],
mbw=mbw,
ret=1,
)
def test_lookup(self):
self.check(['lookup', '-c', 'debug_remoteexec'],
ret=0,
out=('\n'
'Writing """\\\n'
'is_debug = true\n'
'use_remoteexec = 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'
'dict_arg = { bool = true\n'
'list = [ "foo", "bar", "baz" ]\n'
'string = "foo" }\n'
'string_arg = "has double quotes"\n'
'string_list_arg = [ "foo", "bar", "baz" ]\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_remoteexec', '--quiet'],
ret=0,
out=('is_debug = true\n'
'use_remoteexec = true\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_remoteexec = 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', '--force'],
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', '--force'],
mbw=mbw,
ret=0)
self.assertIn(['autoninja.bat', '-C', 'out\\Default', 'base_unittests'],
mbw.calls)
def test_lookup_non_existent_builder_group(self):
"""Ensure correct behavior when non-existent builder group is specified.
Lookups for builders that don't exist in the config file return a different
exit code so that they can be distinguished from other errors.
"""
mbw = self.fake_mbw()
self.check(
[
'lookup', '-m', 'non-existent-builder-group', '-b',
'non-existent-builder'
],
mbw=mbw,
ret=2,
)
self.assertIn(
'MBErr: Builder group name "non-existent-builder-group" not found',
mbw.out)
def test_lookup_non_existent_builder(self):
"""Ensure correct behavior when non-existent builder is specified.
Lookups for builders that don't exist in the config file return a different
exit code so that they can be distinguished from other errors.
"""
mbw = self.fake_mbw()
self.check(
['lookup', '-m', 'fake_builder_group', '-b', 'non-existent-builder'],
mbw=mbw,
ret=2)
self.assertIn(
'MBErr: Builder name "non-existent-builder" not found under groups',
mbw.out)
if __name__ == '__main__':
unittest.main()