Home | History | Annotate | Download | only in mb
      1 #!/usr/bin/python
      2 # Copyright 2016 the V8 project authors. All rights reserved.
      3 # Copyright 2015 The Chromium Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Tests for mb.py."""
      8 
      9 import json
     10 import StringIO
     11 import os
     12 import sys
     13 import unittest
     14 
     15 import mb
     16 
     17 
     18 class FakeMBW(mb.MetaBuildWrapper):
     19   def __init__(self, win32=False):
     20     super(FakeMBW, self).__init__()
     21 
     22     # Override vars for test portability.
     23     if win32:
     24       self.chromium_src_dir = 'c:\\fake_src'
     25       self.default_config = 'c:\\fake_src\\tools\\mb\\mb_config.pyl'
     26       self.default_isolate_map = ('c:\\fake_src\\testing\\buildbot\\'
     27                                   'gn_isolate_map.pyl')
     28       self.platform = 'win32'
     29       self.executable = 'c:\\python\\python.exe'
     30       self.sep = '\\'
     31     else:
     32       self.chromium_src_dir = '/fake_src'
     33       self.default_config = '/fake_src/tools/mb/mb_config.pyl'
     34       self.default_isolate_map = '/fake_src/testing/buildbot/gn_isolate_map.pyl'
     35       self.executable = '/usr/bin/python'
     36       self.platform = 'linux2'
     37       self.sep = '/'
     38 
     39     self.files = {}
     40     self.calls = []
     41     self.cmds = []
     42     self.cross_compile = None
     43     self.out = ''
     44     self.err = ''
     45     self.rmdirs = []
     46 
     47   def ExpandUser(self, path):
     48     return '$HOME/%s' % path
     49 
     50   def Exists(self, path):
     51     return self.files.get(path) is not None
     52 
     53   def MaybeMakeDirectory(self, path):
     54     self.files[path] = True
     55 
     56   def PathJoin(self, *comps):
     57     return self.sep.join(comps)
     58 
     59   def ReadFile(self, path):
     60     return self.files[path]
     61 
     62   def WriteFile(self, path, contents, force_verbose=False):
     63     if self.args.dryrun or self.args.verbose or force_verbose:
     64       self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
     65     self.files[path] = contents
     66 
     67   def Call(self, cmd, env=None, buffer_output=True):
     68     self.calls.append(cmd)
     69     if self.cmds:
     70       return self.cmds.pop(0)
     71     return 0, '', ''
     72 
     73   def Print(self, *args, **kwargs):
     74     sep = kwargs.get('sep', ' ')
     75     end = kwargs.get('end', '\n')
     76     f = kwargs.get('file', sys.stdout)
     77     if f == sys.stderr:
     78       self.err += sep.join(args) + end
     79     else:
     80       self.out += sep.join(args) + end
     81 
     82   def TempFile(self, mode='w'):
     83     return FakeFile(self.files)
     84 
     85   def RemoveFile(self, path):
     86     del self.files[path]
     87 
     88   def RemoveDirectory(self, path):
     89     self.rmdirs.append(path)
     90     files_to_delete = [f for f in self.files if f.startswith(path)]
     91     for f in files_to_delete:
     92       self.files[f] = None
     93 
     94 
     95 class FakeFile(object):
     96   def __init__(self, files):
     97     self.name = '/tmp/file'
     98     self.buf = ''
     99     self.files = files
    100 
    101   def write(self, contents):
    102     self.buf += contents
    103 
    104   def close(self):
    105      self.files[self.name] = self.buf
    106 
    107 
    108 TEST_CONFIG = """\
    109 {
    110   'masters': {
    111     'chromium': {},
    112     'fake_master': {
    113       'fake_builder': 'rel_bot',
    114       'fake_debug_builder': 'debug_goma',
    115       'fake_args_bot': '//build/args/bots/fake_master/fake_args_bot.gn',
    116       'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'},
    117       'fake_args_file': 'args_file_goma',
    118       'fake_args_file_twice': 'args_file_twice',
    119     },
    120   },
    121   'configs': {
    122     'args_file_goma': ['args_file', 'goma'],
    123     'args_file_twice': ['args_file', 'args_file'],
    124     'rel_bot': ['rel', 'goma', 'fake_feature1'],
    125     'debug_goma': ['debug', 'goma'],
    126     'phase_1': ['phase_1'],
    127     'phase_2': ['phase_2'],
    128   },
    129   'mixins': {
    130     'fake_feature1': {
    131       'gn_args': 'enable_doom_melon=true',
    132     },
    133     'goma': {
    134       'gn_args': 'use_goma=true',
    135     },
    136     'args_file': {
    137       'args_file': '//build/args/fake.gn',
    138     },
    139     'phase_1': {
    140       'gn_args': 'phase=1',
    141     },
    142     'phase_2': {
    143       'gn_args': 'phase=2',
    144     },
    145     'rel': {
    146       'gn_args': 'is_debug=false',
    147     },
    148     'debug': {
    149       'gn_args': 'is_debug=true',
    150     },
    151   },
    152 }
    153 """
    154 
    155 
    156 TRYSERVER_CONFIG = """\
    157 {
    158   'masters': {
    159     'not_a_tryserver': {
    160       'fake_builder': 'fake_config',
    161     },
    162     'tryserver.chromium.linux': {
    163       'try_builder': 'fake_config',
    164     },
    165     'tryserver.chromium.mac': {
    166       'try_builder2': 'fake_config',
    167     },
    168   },
    169   'luci_tryservers': {
    170     'luci_tryserver1': ['luci_builder1'],
    171     'luci_tryserver2': ['luci_builder2'],
    172   },
    173   'configs': {},
    174   'mixins': {},
    175 }
    176 """
    177 
    178 
    179 class UnitTest(unittest.TestCase):
    180   def fake_mbw(self, files=None, win32=False):
    181     mbw = FakeMBW(win32=win32)
    182     mbw.files.setdefault(mbw.default_config, TEST_CONFIG)
    183     mbw.files.setdefault(
    184       mbw.ToAbsPath('//testing/buildbot/gn_isolate_map.pyl'),
    185       '''{
    186         "foo_unittests": {
    187           "label": "//foo:foo_unittests",
    188           "type": "console_test_launcher",
    189           "args": [],
    190         },
    191       }''')
    192     mbw.files.setdefault(
    193         mbw.ToAbsPath('//build/args/bots/fake_master/fake_args_bot.gn'),
    194         'is_debug = false\n')
    195     if files:
    196       for path, contents in files.items():
    197         mbw.files[path] = contents
    198     return mbw
    199 
    200   def check(self, args, mbw=None, files=None, out=None, err=None, ret=None):
    201     if not mbw:
    202       mbw = self.fake_mbw(files)
    203 
    204     actual_ret = mbw.Main(args)
    205 
    206     self.assertEqual(actual_ret, ret)
    207     if out is not None:
    208       self.assertEqual(mbw.out, out)
    209     if err is not None:
    210       self.assertEqual(mbw.err, err)
    211     return mbw
    212 
    213   def test_analyze(self):
    214     files = {'/tmp/in.json': '''{\
    215                "files": ["foo/foo_unittest.cc"],
    216                "test_targets": ["foo_unittests"],
    217                "additional_compile_targets": ["all"]
    218              }''',
    219              '/tmp/out.json.gn': '''{\
    220                "status": "Found dependency",
    221                "compile_targets": ["//foo:foo_unittests"],
    222                "test_targets": ["//foo:foo_unittests"]
    223              }'''}
    224 
    225     mbw = self.fake_mbw(files)
    226     mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
    227 
    228     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
    229                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
    230     out = json.loads(mbw.files['/tmp/out.json'])
    231     self.assertEqual(out, {
    232       'status': 'Found dependency',
    233       'compile_targets': ['foo:foo_unittests'],
    234       'test_targets': ['foo_unittests']
    235     })
    236 
    237   def test_analyze_optimizes_compile_for_all(self):
    238     files = {'/tmp/in.json': '''{\
    239                "files": ["foo/foo_unittest.cc"],
    240                "test_targets": ["foo_unittests"],
    241                "additional_compile_targets": ["all"]
    242              }''',
    243              '/tmp/out.json.gn': '''{\
    244                "status": "Found dependency",
    245                "compile_targets": ["//foo:foo_unittests", "all"],
    246                "test_targets": ["//foo:foo_unittests"]
    247              }'''}
    248 
    249     mbw = self.fake_mbw(files)
    250     mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
    251 
    252     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
    253                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
    254     out = json.loads(mbw.files['/tmp/out.json'])
    255 
    256     # check that 'foo_unittests' is not in the compile_targets
    257     self.assertEqual(['all'], out['compile_targets'])
    258 
    259   def test_analyze_handles_other_toolchains(self):
    260     files = {'/tmp/in.json': '''{\
    261                "files": ["foo/foo_unittest.cc"],
    262                "test_targets": ["foo_unittests"],
    263                "additional_compile_targets": ["all"]
    264              }''',
    265              '/tmp/out.json.gn': '''{\
    266                "status": "Found dependency",
    267                "compile_targets": ["//foo:foo_unittests",
    268                                    "//foo:foo_unittests(bar)"],
    269                "test_targets": ["//foo:foo_unittests"]
    270              }'''}
    271 
    272     mbw = self.fake_mbw(files)
    273     mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
    274 
    275     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
    276                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
    277     out = json.loads(mbw.files['/tmp/out.json'])
    278 
    279     # crbug.com/736215: If GN returns a label containing a toolchain,
    280     # MB (and Ninja) don't know how to handle it; to work around this,
    281     # we give up and just build everything we were asked to build. The
    282     # output compile_targets should include all of the input test_targets and
    283     # additional_compile_targets.
    284     self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
    285 
    286   def test_analyze_handles_way_too_many_results(self):
    287     too_many_files = ', '.join(['"//foo:foo%d"' % i for i in xrange(4 * 1024)])
    288     files = {'/tmp/in.json': '''{\
    289                "files": ["foo/foo_unittest.cc"],
    290                "test_targets": ["foo_unittests"],
    291                "additional_compile_targets": ["all"]
    292              }''',
    293              '/tmp/out.json.gn': '''{\
    294                "status": "Found dependency",
    295                "compile_targets": [''' + too_many_files + '''],
    296                "test_targets": ["//foo:foo_unittests"]
    297              }'''}
    298 
    299     mbw = self.fake_mbw(files)
    300     mbw.Call = lambda cmd, env=None, buffer_output=True: (0, '', '')
    301 
    302     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
    303                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
    304     out = json.loads(mbw.files['/tmp/out.json'])
    305 
    306     # If GN returns so many compile targets that we might have command-line
    307     # issues, we should give up and just build everything we were asked to
    308     # build. The output compile_targets should include all of the input
    309     # test_targets and additional_compile_targets.
    310     self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
    311 
    312   def test_gen(self):
    313     mbw = self.fake_mbw()
    314     self.check(['gen', '-c', 'debug_goma', '//out/Default', '-g', '/goma'],
    315                mbw=mbw, ret=0)
    316     self.assertMultiLineEqual(mbw.files['/fake_src/out/Default/args.gn'],
    317                               ('goma_dir = "/goma"\n'
    318                                'is_debug = true\n'
    319                                'use_goma = true\n'))
    320 
    321     # Make sure we log both what is written to args.gn and the command line.
    322     self.assertIn('Writing """', mbw.out)
    323     self.assertIn('/fake_src/buildtools/linux64/gn gen //out/Default --check',
    324                   mbw.out)
    325 
    326     mbw = self.fake_mbw(win32=True)
    327     self.check(['gen', '-c', 'debug_goma', '-g', 'c:\\goma', '//out/Debug'],
    328                mbw=mbw, ret=0)
    329     self.assertMultiLineEqual(mbw.files['c:\\fake_src\\out\\Debug\\args.gn'],
    330                               ('goma_dir = "c:\\\\goma"\n'
    331                                'is_debug = true\n'
    332                                'use_goma = true\n'))
    333     self.assertIn('c:\\fake_src\\buildtools\\win\\gn.exe gen //out/Debug '
    334                   '--check\n', mbw.out)
    335 
    336     mbw = self.fake_mbw()
    337     self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_bot',
    338                 '//out/Debug'],
    339                mbw=mbw, ret=0)
    340     self.assertEqual(
    341         mbw.files['/fake_src/out/Debug/args.gn'],
    342         'import("//build/args/bots/fake_master/fake_args_bot.gn")\n')
    343 
    344   def test_gen_args_file_mixins(self):
    345     mbw = self.fake_mbw()
    346     self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_file',
    347                 '//out/Debug'], mbw=mbw, ret=0)
    348 
    349     self.assertEqual(
    350         mbw.files['/fake_src/out/Debug/args.gn'],
    351         ('import("//build/args/fake.gn")\n'
    352          'use_goma = true\n'))
    353 
    354     mbw = self.fake_mbw()
    355     self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_file_twice',
    356                 '//out/Debug'], mbw=mbw, ret=1)
    357 
    358   def test_gen_fails(self):
    359     mbw = self.fake_mbw()
    360     mbw.Call = lambda cmd, env=None, buffer_output=True: (1, '', '')
    361     self.check(['gen', '-c', 'debug_goma', '//out/Default'], mbw=mbw, ret=1)
    362 
    363   def test_gen_swarming(self):
    364     files = {
    365       '/tmp/swarming_targets': 'base_unittests\n',
    366       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    367           "{'base_unittests': {"
    368           "  'label': '//base:base_unittests',"
    369           "  'type': 'raw',"
    370           "  'args': [],"
    371           "}}\n"
    372       ),
    373       '/fake_src/out/Default/base_unittests.runtime_deps': (
    374           "base_unittests\n"
    375       ),
    376     }
    377     mbw = self.fake_mbw(files)
    378     self.check(['gen',
    379                 '-c', 'debug_goma',
    380                 '--swarming-targets-file', '/tmp/swarming_targets',
    381                 '//out/Default'], mbw=mbw, ret=0)
    382     self.assertIn('/fake_src/out/Default/base_unittests.isolate',
    383                   mbw.files)
    384     self.assertIn('/fake_src/out/Default/base_unittests.isolated.gen.json',
    385                   mbw.files)
    386 
    387   def test_gen_swarming_script(self):
    388     files = {
    389       '/tmp/swarming_targets': 'cc_perftests\n',
    390       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    391           "{'cc_perftests': {"
    392           "  'label': '//cc:cc_perftests',"
    393           "  'type': 'script',"
    394           "  'script': '/fake_src/out/Default/test_script.py',"
    395           "  'args': [],"
    396           "}}\n"
    397       ),
    398       'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps': (
    399           "cc_perftests\n"
    400       ),
    401     }
    402     mbw = self.fake_mbw(files=files, win32=True)
    403     self.check(['gen',
    404                 '-c', 'debug_goma',
    405                 '--swarming-targets-file', '/tmp/swarming_targets',
    406                 '--isolate-map-file',
    407                 '/fake_src/testing/buildbot/gn_isolate_map.pyl',
    408                 '//out/Default'], mbw=mbw, ret=0)
    409     self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolate',
    410                   mbw.files)
    411     self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolated.gen.json',
    412                   mbw.files)
    413 
    414 
    415   def test_multiple_isolate_maps(self):
    416     files = {
    417       '/tmp/swarming_targets': 'cc_perftests\n',
    418       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    419           "{'cc_perftests': {"
    420           "  'label': '//cc:cc_perftests',"
    421           "  'type': 'raw',"
    422           "  'args': [],"
    423           "}}\n"
    424       ),
    425       '/fake_src/testing/buildbot/gn_isolate_map2.pyl': (
    426           "{'cc_perftests2': {"
    427           "  'label': '//cc:cc_perftests',"
    428           "  'type': 'raw',"
    429           "  'args': [],"
    430           "}}\n"
    431       ),
    432       'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps': (
    433           "cc_perftests\n"
    434       ),
    435     }
    436     mbw = self.fake_mbw(files=files, win32=True)
    437     self.check(['gen',
    438                 '-c', 'debug_goma',
    439                 '--swarming-targets-file', '/tmp/swarming_targets',
    440                 '--isolate-map-file',
    441                 '/fake_src/testing/buildbot/gn_isolate_map.pyl',
    442                 '--isolate-map-file',
    443                 '/fake_src/testing/buildbot/gn_isolate_map2.pyl',
    444                 '//out/Default'], mbw=mbw, ret=0)
    445     self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolate',
    446                   mbw.files)
    447     self.assertIn('c:\\fake_src\\out\\Default\\cc_perftests.isolated.gen.json',
    448                   mbw.files)
    449 
    450 
    451   def test_duplicate_isolate_maps(self):
    452     files = {
    453       '/tmp/swarming_targets': 'cc_perftests\n',
    454       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    455           "{'cc_perftests': {"
    456           "  'label': '//cc:cc_perftests',"
    457           "  'type': 'raw',"
    458           "  'args': [],"
    459           "}}\n"
    460       ),
    461       '/fake_src/testing/buildbot/gn_isolate_map2.pyl': (
    462           "{'cc_perftests': {"
    463           "  'label': '//cc:cc_perftests',"
    464           "  'type': 'raw',"
    465           "  'args': [],"
    466           "}}\n"
    467       ),
    468       'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps': (
    469           "cc_perftests\n"
    470       ),
    471     }
    472     mbw = self.fake_mbw(files=files, win32=True)
    473     # Check that passing duplicate targets into mb fails.
    474     self.check(['gen',
    475                 '-c', 'debug_goma',
    476                 '--swarming-targets-file', '/tmp/swarming_targets',
    477                 '--isolate-map-file',
    478                 '/fake_src/testing/buildbot/gn_isolate_map.pyl',
    479                 '--isolate-map-file',
    480                 '/fake_src/testing/buildbot/gn_isolate_map2.pyl',
    481                 '//out/Default'], mbw=mbw, ret=1)
    482 
    483   def test_isolate(self):
    484     files = {
    485       '/fake_src/out/Default/toolchain.ninja': "",
    486       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    487           "{'base_unittests': {"
    488           "  'label': '//base:base_unittests',"
    489           "  'type': 'raw',"
    490           "  'args': [],"
    491           "}}\n"
    492       ),
    493       '/fake_src/out/Default/base_unittests.runtime_deps': (
    494           "base_unittests\n"
    495       ),
    496     }
    497     self.check(['isolate', '-c', 'debug_goma', '//out/Default',
    498                 'base_unittests'], files=files, ret=0)
    499 
    500     # test running isolate on an existing build_dir
    501     files['/fake_src/out/Default/args.gn'] = 'is_debug = True\n'
    502     self.check(['isolate', '//out/Default', 'base_unittests'],
    503                files=files, ret=0)
    504 
    505     self.check(['isolate', '//out/Default', 'base_unittests'],
    506                files=files, ret=0)
    507 
    508   def test_run(self):
    509     files = {
    510       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    511           "{'base_unittests': {"
    512           "  'label': '//base:base_unittests',"
    513           "  'type': 'raw',"
    514           "  'args': [],"
    515           "}}\n"
    516       ),
    517       '/fake_src/out/Default/base_unittests.runtime_deps': (
    518           "base_unittests\n"
    519       ),
    520     }
    521     self.check(['run', '-c', 'debug_goma', '//out/Default',
    522                 'base_unittests'], files=files, ret=0)
    523 
    524   def test_run_swarmed(self):
    525     files = {
    526       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
    527           "{'base_unittests': {"
    528           "  'label': '//base:base_unittests',"
    529           "  'type': 'raw',"
    530           "  'args': [],"
    531           "}}\n"
    532       ),
    533       '/fake_src/out/Default/base_unittests.runtime_deps': (
    534           "base_unittests\n"
    535       ),
    536     }
    537 
    538     def run_stub(cmd, **_kwargs):
    539       if 'isolate.py' in cmd[1]:
    540         return 0, 'fake_hash base_unittests', ''
    541       else:
    542         return 0, '', ''
    543 
    544     mbw = self.fake_mbw(files=files)
    545     mbw.Run = run_stub
    546     self.check(['run', '-s', '-c', 'debug_goma', '//out/Default',
    547                 'base_unittests'], mbw=mbw, ret=0)
    548     self.check(['run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7',
    549                 '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
    550 
    551   def test_lookup(self):
    552     self.check(['lookup', '-c', 'debug_goma'], ret=0)
    553 
    554   def test_lookup_goma_dir_expansion(self):
    555     self.check(['lookup', '-c', 'rel_bot', '-g', '/foo'], ret=0,
    556                out=('\n'
    557                     'Writing """\\\n'
    558                     'enable_doom_melon = true\n'
    559                     'goma_dir = "/foo"\n'
    560                     'is_debug = false\n'
    561                     'use_goma = true\n'
    562                     '""" to _path_/args.gn.\n\n'
    563                     '/fake_src/buildtools/linux64/gn gen _path_\n'))
    564 
    565   def test_help(self):
    566     orig_stdout = sys.stdout
    567     try:
    568       sys.stdout = StringIO.StringIO()
    569       self.assertRaises(SystemExit, self.check, ['-h'])
    570       self.assertRaises(SystemExit, self.check, ['help'])
    571       self.assertRaises(SystemExit, self.check, ['help', 'gen'])
    572     finally:
    573       sys.stdout = orig_stdout
    574 
    575   def test_multiple_phases(self):
    576     # Check that not passing a --phase to a multi-phase builder fails.
    577     mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_multi_phase'],
    578                      ret=1)
    579     self.assertIn('Must specify a build --phase', mbw.out)
    580 
    581     # Check that passing a --phase to a single-phase builder fails.
    582     mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_builder',
    583                       '--phase', 'phase_1'], ret=1)
    584     self.assertIn('Must not specify a build --phase', mbw.out)
    585 
    586     # Check that passing a wrong phase key to a multi-phase builder fails.
    587     mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_multi_phase',
    588                       '--phase', 'wrong_phase'], ret=1)
    589     self.assertIn('Phase wrong_phase doesn\'t exist', mbw.out)
    590 
    591     # Check that passing a correct phase key to a multi-phase builder passes.
    592     mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_multi_phase',
    593                       '--phase', 'phase_1'], ret=0)
    594     self.assertIn('phase = 1', mbw.out)
    595 
    596     mbw = self.check(['lookup', '-m', 'fake_master', '-b', 'fake_multi_phase',
    597                       '--phase', 'phase_2'], ret=0)
    598     self.assertIn('phase = 2', mbw.out)
    599 
    600   def test_validate(self):
    601     mbw = self.fake_mbw()
    602     self.check(['validate'], mbw=mbw, ret=0)
    603 
    604   def test_buildbucket(self):
    605     mbw = self.fake_mbw()
    606     mbw.files[mbw.default_config] = TRYSERVER_CONFIG
    607     self.check(['gerrit-buildbucket-config'], mbw=mbw,
    608                ret=0,
    609                out=('# This file was generated using '
    610                     '"tools/mb/mb.py gerrit-buildbucket-config".\n'
    611                     '[bucket "luci.luci_tryserver1"]\n'
    612                     '\tbuilder = luci_builder1\n'
    613                     '[bucket "luci.luci_tryserver2"]\n'
    614                     '\tbuilder = luci_builder2\n'
    615                     '[bucket "master.tryserver.chromium.linux"]\n'
    616                     '\tbuilder = try_builder\n'
    617                     '[bucket "master.tryserver.chromium.mac"]\n'
    618                     '\tbuilder = try_builder2\n'))
    619 
    620 
    621 if __name__ == '__main__':
    622   unittest.main()
    623