Revert "[M120 Migration]Fix for crash during chrome exit"
[platform/framework/web/chromium-efl.git] / tools / mb / mb_unittest.py
1 #!/usr/bin/env python3
2 # Copyright 2020 The Chromium Authors
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Tests for mb.py."""
7
8 import io
9 import json
10 import os
11 import re
12 import sys
13 import textwrap
14 import unittest
15
16 sys.path.insert(
17     0,
18     os.path.abspath(
19         os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')))
20
21 from mb import mb
22
23 # Call has argument input to match subprocess.run
24 # pylint: disable=redefined-builtin
25
26
27 class FakeMBW(mb.MetaBuildWrapper):
28   def __init__(self, win32=False):
29     super().__init__()
30
31     # Override vars for test portability.
32     if win32:
33       self.chromium_src_dir = 'c:\\fake_src'
34       self.default_config = 'c:\\fake_src\\tools\\mb\\mb_config.pyl'
35       self.default_isolate_map = ('c:\\fake_src\\testing\\buildbot\\'
36                                   'gn_isolate_map.pyl')
37       self.temp = 'c:\\temp'
38       self.platform = 'win32'
39       self.executable = 'c:\\python\\python.exe'
40       self.sep = '\\'
41       self.cwd = 'c:\\fake_src\\out\\Default'
42     else:
43       self.chromium_src_dir = '/fake_src'
44       self.default_config = '/fake_src/tools/mb/mb_config.pyl'
45       self.default_isolate_map = '/fake_src/testing/buildbot/gn_isolate_map.pyl'
46       self.temp = '/tmp'
47       self.platform = 'linux'
48       self.executable = '/usr/bin/python'
49       self.sep = '/'
50       self.cwd = '/fake_src/out/Default'
51
52     self.files = {}
53     self.dirs = set()
54     self.calls = []
55     self.cmds = []
56     self.cross_compile = None
57     self.out = ''
58     self.err = ''
59     self.rmdirs = []
60
61   def Exists(self, path):
62     abs_path = self._AbsPath(path)
63     return (self.files.get(abs_path) is not None or abs_path in self.dirs)
64
65   def ListDir(self, path):
66     dir_contents = []
67     for f in list(self.files.keys()) + list(self.dirs):
68       head, _ = os.path.split(f)
69       if head == path:
70         dir_contents.append(f)
71     return dir_contents
72
73   def MaybeMakeDirectory(self, path):
74     abpath = self._AbsPath(path)
75     self.dirs.add(abpath)
76
77   def PathJoin(self, *comps):
78     return self.sep.join(comps)
79
80   def ReadFile(self, path):
81     try:
82       return self.files[self._AbsPath(path)]
83     except KeyError as e:
84       raise IOError('%s not found' % path) from e
85
86   def WriteFile(self, path, contents, force_verbose=False):
87     if self.args.dryrun or self.args.verbose or force_verbose:
88       self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
89     abpath = self._AbsPath(path)
90     self.files[abpath] = contents
91
92   def Call(self, cmd, env=None, capture_output=True, input=None):
93     # Avoid unused-argument warnings from Pylint
94     del env
95     del capture_output
96     del input
97     self.calls.append(cmd)
98     if self.cmds:
99       return self.cmds.pop(0)
100     return 0, '', ''
101
102   def Print(self, *args, **kwargs):
103     sep = kwargs.get('sep', ' ')
104     end = kwargs.get('end', '\n')
105     f = kwargs.get('file', sys.stdout)
106     if f == sys.stderr:
107       self.err += sep.join(args) + end
108     else:
109       self.out += sep.join(args) + end
110
111   def TempDir(self):
112     tmp_dir = self.temp + self.sep + 'mb_test'
113     self.dirs.add(tmp_dir)
114     return tmp_dir
115
116   def TempFile(self, mode='w'):
117     # Avoid unused-argument warnings from Pylint
118     del mode
119     return FakeFile(self.files)
120
121   def RemoveFile(self, path):
122     abpath = self._AbsPath(path)
123     self.files[abpath] = None
124
125   def RemoveDirectory(self, abs_path):
126     # Normalize the passed-in path to handle different working directories
127     # used during unit testing.
128     abs_path = self._AbsPath(abs_path)
129     self.rmdirs.append(abs_path)
130     files_to_delete = [f for f in self.files if f.startswith(abs_path)]
131     for f in files_to_delete:
132       self.files[f] = None
133
134   def _AbsPath(self, path):
135     if not ((self.platform == 'win32' and path.startswith('c:')) or
136             (self.platform != 'win32' and path.startswith('/'))):
137       path = self.PathJoin(self.cwd, path)
138     if self.sep == '\\':
139       return re.sub(r'\\+', r'\\', path)
140     return re.sub('/+', '/', path)
141
142
143 class FakeFile:
144   def __init__(self, files):
145     self.name = '/tmp/file'
146     self.buf = ''
147     self.files = files
148
149   def write(self, contents):
150     self.buf += contents
151
152   def close(self):
153     self.files[self.name] = self.buf
154
155
156 TEST_CONFIG = """\
157 {
158   'builder_groups': {
159     'chromium': {},
160     'fake_builder_group': {
161       'fake_args_bot': 'fake_args_bot',
162       'fake_args_file': 'args_file_goma',
163       'fake_builder': 'rel_bot',
164       'fake_debug_builder': 'debug_goma',
165       'fake_ios_error': 'ios_error',
166       'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'},
167     },
168   },
169   'configs': {
170     'args_file_goma': ['fake_args_bot', 'goma'],
171     'debug_goma': ['debug', 'goma'],
172     'fake_args_bot': ['fake_args_bot'],
173     'ios_error': ['error'],
174     'phase_1': ['rel', 'phase_1'],
175     'phase_2': ['rel', 'phase_2'],
176     'rel_bot': ['rel', 'goma', 'fake_feature1'],
177   },
178   'mixins': {
179     'debug': {
180       'gn_args': 'is_debug=true',
181     },
182     'error': {
183       'gn_args': 'error',
184     },
185     'fake_args_bot': {
186       'args_file': '//build/args/bots/fake_builder_group/fake_args_bot.gn',
187     },
188     'fake_feature1': {
189       'gn_args': 'enable_doom_melon=true',
190     },
191     'goma': {
192       'gn_args': 'use_goma=true',
193     },
194     'phase_1': {
195       'gn_args': 'phase=1',
196     },
197     'phase_2': {
198       'gn_args': 'phase=2',
199     },
200     'rel': {
201       'gn_args': 'is_debug=false dcheck_always_on=false',
202     },
203   },
204 }
205 """
206
207 CONFIG_STARLARK_GN_ARGS = """\
208 {
209   'gn_args_locations_files': [
210       '../../infra/config/generated/builders/gn_args_locations.json',
211   ],
212   'builder_groups': {
213   },
214   'configs': {
215   },
216   'mixins': {
217   },
218 }
219 """
220
221 TEST_GN_ARGS_LOCATIONS_JSON = """\
222 {
223   "chromium": {
224     "linux-official": "ci/linux-official/gn-args.json"
225   },
226   "tryserver.chromium": {
227     "linux-official": "try/linux-official/gn-args.json"
228   }
229 }
230 """
231
232 TEST_GN_ARGS_JSON = """\
233 {
234   "gn_args": {
235     "string_arg": "has double quotes",
236     "bool_arg_lower_case": true
237   }
238 }
239 """
240
241 TEST_PHASED_GN_ARGS_JSON = """\
242 {
243   "phases": {
244     "phase_1": {
245       "gn_args": {
246         "string_arg": "has double quotes",
247         "bool_arg_lower_case": true
248       }
249     },
250     "phase_2": {
251       "gn_args": {
252         "string_arg": "second phase",
253         "bool_arg_lower_case": false
254       }
255     }
256   }
257 }
258 """
259
260 TEST_BAD_CONFIG = """\
261 {
262   'configs': {
263     'rel_bot_1': ['rel', 'chrome_with_codecs'],
264     'rel_bot_2': ['rel', 'bad_nested_config'],
265   },
266   'builder_groups': {
267     'chromium': {
268       'a': 'rel_bot_1',
269       'b': 'rel_bot_2',
270     },
271   },
272   'mixins': {
273     'chrome_with_codecs': {
274       'gn_args': 'proprietary_codecs=true',
275     },
276     'bad_nested_config': {
277       'mixins': ['chrome_with_codecs'],
278     },
279     'rel': {
280       'gn_args': 'is_debug=false',
281     },
282   },
283 }
284 """
285
286
287
288 TEST_ARGS_FILE_TWICE_CONFIG = """\
289 {
290   'builder_groups': {
291     'chromium': {},
292     'fake_builder_group': {
293       'fake_args_file_twice': 'args_file_twice',
294     },
295   },
296   'configs': {
297     'args_file_twice': ['args_file', 'args_file'],
298   },
299   'mixins': {
300     'args_file': {
301       'args_file': '//build/args/fake.gn',
302     },
303   },
304 }
305 """
306
307
308 TEST_DUP_CONFIG = """\
309 {
310   'builder_groups': {
311     'chromium': {},
312     'fake_builder_group': {
313       'fake_builder': 'some_config',
314       'other_builder': 'some_other_config',
315     },
316   },
317   'configs': {
318     'some_config': ['args_file'],
319     'some_other_config': ['args_file'],
320   },
321   'mixins': {
322     'args_file': {
323       'args_file': '//build/args/fake.gn',
324     },
325   },
326 }
327 """
328
329 TRYSERVER_CONFIG = """\
330 {
331   'builder_groups': {
332     'not_a_tryserver': {
333       'fake_builder': 'fake_config',
334     },
335     'tryserver.chromium.linux': {
336       'try_builder': 'fake_config',
337     },
338     'tryserver.chromium.mac': {
339       'try_builder2': 'fake_config',
340     },
341   },
342   'configs': {},
343   'mixins': {},
344 }
345 """
346
347
348 def is_win():
349   return sys.platform == 'win32'
350
351
352 class UnitTest(unittest.TestCase):
353   maxDiff = None
354
355   def fake_mbw(self, files=None, win32=False):
356     mbw = FakeMBW(win32=win32)
357     mbw.files.setdefault(mbw.default_config, TEST_CONFIG)
358     mbw.files.setdefault(
359       mbw.ToAbsPath('//testing/buildbot/gn_isolate_map.pyl'),
360       '''{
361         "foo_unittests": {
362           "label": "//foo:foo_unittests",
363           "type": "console_test_launcher",
364           "args": [],
365         },
366       }''')
367     mbw.files.setdefault(
368         mbw.ToAbsPath('//build/args/bots/fake_builder_group/fake_args_bot.gn'),
369         'is_debug = false\ndcheck_always_on=false\n')
370     mbw.files.setdefault(mbw.ToAbsPath('//tools/mb/rts_banned_suites.json'),
371                          '{}')
372     if files:
373       for path, contents in files.items():
374         mbw.files[path] = contents
375     return mbw
376
377   def check(self, args, mbw=None, files=None, out=None, err=None, ret=None,
378             env=None):
379     if not mbw:
380       mbw = self.fake_mbw(files)
381
382     try:
383       prev_env = os.environ.copy()
384       os.environ = env if env else prev_env
385       actual_ret = mbw.Main(args)
386     finally:
387       os.environ = prev_env
388     self.assertEqual(
389         actual_ret, ret,
390         "ret: %s, out: %s, err: %s" % (actual_ret, mbw.out, mbw.err))
391     if out is not None:
392       self.assertEqual(mbw.out, out)
393     if err is not None:
394       self.assertEqual(mbw.err, err)
395     return mbw
396
397   def path(self, p):
398     if is_win():
399       return 'c:' + p.replace('/', '\\')
400     return p
401
402   def test_analyze(self):
403     files = {'/tmp/in.json': '''{\
404                "files": ["foo/foo_unittest.cc"],
405                "test_targets": ["foo_unittests"],
406                "additional_compile_targets": ["all"]
407              }''',
408              '/tmp/out.json.gn': '''{\
409                "status": "Found dependency",
410                "compile_targets": ["//foo:foo_unittests"],
411                "test_targets": ["//foo:foo_unittests"]
412              }'''}
413
414     mbw = self.fake_mbw(files)
415     mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
416
417     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
418                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
419     out = json.loads(mbw.files['/tmp/out.json'])
420     self.assertEqual(out, {
421       'status': 'Found dependency',
422       'compile_targets': ['foo:foo_unittests'],
423       'test_targets': ['foo_unittests']
424     })
425
426   def test_analyze_optimizes_compile_for_all(self):
427     files = {'/tmp/in.json': '''{\
428                "files": ["foo/foo_unittest.cc"],
429                "test_targets": ["foo_unittests"],
430                "additional_compile_targets": ["all"]
431              }''',
432              '/tmp/out.json.gn': '''{\
433                "status": "Found dependency",
434                "compile_targets": ["//foo:foo_unittests", "all"],
435                "test_targets": ["//foo:foo_unittests"]
436              }'''}
437
438     mbw = self.fake_mbw(files)
439     mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
440
441     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
442                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
443     out = json.loads(mbw.files['/tmp/out.json'])
444
445     # check that 'foo_unittests' is not in the compile_targets
446     self.assertEqual(['all'], out['compile_targets'])
447
448   def test_analyze_handles_other_toolchains(self):
449     files = {'/tmp/in.json': '''{\
450                "files": ["foo/foo_unittest.cc"],
451                "test_targets": ["foo_unittests"],
452                "additional_compile_targets": ["all"]
453              }''',
454              '/tmp/out.json.gn': '''{\
455                "status": "Found dependency",
456                "compile_targets": ["//foo:foo_unittests",
457                                    "//foo:foo_unittests(bar)"],
458                "test_targets": ["//foo:foo_unittests"]
459              }'''}
460
461     mbw = self.fake_mbw(files)
462     mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
463
464     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
465                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
466     out = json.loads(mbw.files['/tmp/out.json'])
467
468     # crbug.com/736215: If GN returns a label containing a toolchain,
469     # MB (and Ninja) don't know how to handle it; to work around this,
470     # we give up and just build everything we were asked to build. The
471     # output compile_targets should include all of the input test_targets and
472     # additional_compile_targets.
473     self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
474
475   def test_analyze_handles_way_too_many_results(self):
476     too_many_files = ', '.join(['"//foo:foo%d"' % i for i in range(40 * 1024)])
477     files = {'/tmp/in.json': '''{\
478                "files": ["foo/foo_unittest.cc"],
479                "test_targets": ["foo_unittests"],
480                "additional_compile_targets": ["all"]
481              }''',
482              '/tmp/out.json.gn': '''{\
483                "status": "Found dependency",
484                "compile_targets": [''' + too_many_files + '''],
485                "test_targets": ["//foo:foo_unittests"]
486              }'''}
487
488     mbw = self.fake_mbw(files)
489     mbw.Call = lambda cmd, env=None, capture_output=True, input='': (0, '', '')
490
491     self.check(['analyze', '-c', 'debug_goma', '//out/Default',
492                 '/tmp/in.json', '/tmp/out.json'], mbw=mbw, ret=0)
493     out = json.loads(mbw.files['/tmp/out.json'])
494
495     # If GN returns so many compile targets that we might have command-line
496     # issues, we should give up and just build everything we were asked to
497     # build. The output compile_targets should include all of the input
498     # test_targets and additional_compile_targets.
499     self.assertEqual(['all', 'foo_unittests'], out['compile_targets'])
500
501   def test_gen(self):
502     mbw = self.fake_mbw()
503     self.check(['gen', '-c', 'debug_goma', '//out/Default', '-g', '/goma'],
504                mbw=mbw, ret=0)
505     self.assertMultiLineEqual(mbw.files['/fake_src/out/Default/args.gn'],
506                               ('goma_dir = "/goma"\n'
507                                'is_debug = true\n'
508                                'use_goma = true\n'))
509
510     # Make sure we log both what is written to args.gn and the command line.
511     self.assertIn('Writing """', mbw.out)
512     self.assertIn('/fake_src/buildtools/linux64/gn gen //out/Default --check',
513                   mbw.out)
514
515     mbw = self.fake_mbw(win32=True)
516     self.check(['gen', '-c', 'debug_goma', '-g', 'c:\\goma', '//out/Debug'],
517                mbw=mbw, ret=0)
518     self.assertMultiLineEqual(mbw.files['c:\\fake_src\\out\\Debug\\args.gn'],
519                               ('goma_dir = "c:\\\\goma"\n'
520                                'is_debug = true\n'
521                                'use_goma = true\n'))
522     self.assertIn(
523         'c:\\fake_src\\buildtools\\win\\gn.exe gen //out/Debug '
524         '--check', mbw.out)
525
526     mbw = self.fake_mbw()
527     self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_bot',
528                 '//out/Debug'],
529                mbw=mbw, ret=0)
530     # TODO(https://crbug.com/1093038): This assert is inappropriately failing.
531     # self.assertEqual(
532     #     mbw.files['/fake_src/out/Debug/args.gn'],
533     #     'import("//build/args/bots/fake_builder_group/fake_args_bot.gn")\n')
534
535   def test_gen_args_file_mixins(self):
536     mbw = self.fake_mbw()
537     self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_file',
538                 '//out/Debug'], mbw=mbw, ret=0)
539
540     self.assertEqual(
541         mbw.files['/fake_src/out/Debug/args.gn'],
542         ('import("//build/args/bots/fake_builder_group/fake_args_bot.gn")\n'
543          'use_goma = true\n'))
544
545   def test_gen_args_file_twice(self):
546     mbw = self.fake_mbw()
547     mbw.files[mbw.default_config] = TEST_ARGS_FILE_TWICE_CONFIG
548     self.check(['gen', '-m', 'fake_builder_group', '-b', 'fake_args_file_twice',
549                 '//out/Debug'], mbw=mbw, ret=1)
550
551   def test_gen_fails(self):
552     mbw = self.fake_mbw()
553     mbw.Call = lambda cmd, env=None, capture_output=True, input='': (1, '', '')
554     self.check(['gen', '-c', 'debug_goma', '//out/Default'], mbw=mbw, ret=1)
555
556   def test_gen_swarming(self):
557     files = {
558         '/tmp/swarming_targets':
559         'base_unittests\n',
560         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
561         ("{'base_unittests': {"
562          "  'label': '//base:base_unittests',"
563          "  'type': 'console_test_launcher',"
564          "}}\n"),
565     }
566
567     mbw = self.fake_mbw(files)
568
569     def fake_call(cmd, env=None, capture_output=True, input=''):
570       del cmd
571       del env
572       del capture_output
573       del input
574       mbw.files['/fake_src/out/Default/base_unittests.runtime_deps'] = (
575           'base_unittests\n')
576       return 0, '', ''
577
578     mbw.Call = fake_call
579
580     self.check(['gen',
581                 '-c', 'debug_goma',
582                 '--swarming-targets-file', '/tmp/swarming_targets',
583                 '//out/Default'], mbw=mbw, ret=0)
584     self.assertIn('/fake_src/out/Default/base_unittests.isolate',
585                   mbw.files)
586     self.assertIn('/fake_src/out/Default/base_unittests.isolated.gen.json',
587                   mbw.files)
588
589   def test_gen_swarming_script(self):
590     files = {
591       '/tmp/swarming_targets': 'cc_perftests\n',
592       '/fake_src/testing/buildbot/gn_isolate_map.pyl': (
593           "{'cc_perftests': {"
594           "  'label': '//cc:cc_perftests',"
595           "  'type': 'script',"
596           "  'script': '/fake_src/out/Default/test_script.py',"
597           "}}\n"
598       ),
599     }
600     mbw = self.fake_mbw(files=files)
601
602     def fake_call(cmd, env=None, capture_output=True, input=''):
603       del cmd
604       del env
605       del capture_output
606       del input
607       mbw.files['/fake_src/out/Default/cc_perftests.runtime_deps'] = (
608           'cc_perftests\n')
609       return 0, '', ''
610
611     mbw.Call = fake_call
612
613     self.check(['gen',
614                 '-c', 'debug_goma',
615                 '--swarming-targets-file', '/tmp/swarming_targets',
616                 '--isolate-map-file',
617                 '/fake_src/testing/buildbot/gn_isolate_map.pyl',
618                 '//out/Default'], mbw=mbw, ret=0)
619     self.assertIn('/fake_src/out/Default/cc_perftests.isolate',
620                   mbw.files)
621     self.assertIn('/fake_src/out/Default/cc_perftests.isolated.gen.json',
622                   mbw.files)
623
624   def test_multiple_isolate_maps(self):
625     files = {
626         '/tmp/swarming_targets':
627         'cc_perftests\n',
628         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
629         ("{'cc_perftests': {"
630          "  'label': '//cc:cc_perftests',"
631          "  'type': 'console_test_launcher',"
632          "}}\n"),
633         '/fake_src/testing/buildbot/gn_isolate_map2.pyl':
634         ("{'cc_perftests2': {"
635          "  'label': '//cc:cc_perftests',"
636          "  'type': 'console_test_launcher',"
637          "}}\n"),
638     }
639     mbw = self.fake_mbw(files=files)
640
641     def fake_call(cmd, env=None, capture_output=True, input=''):
642       del cmd
643       del env
644       del capture_output
645       del input
646       mbw.files['/fake_src/out/Default/cc_perftests.runtime_deps'] = (
647           'cc_perftests_fuzzer\n')
648       return 0, '', ''
649
650     mbw.Call = fake_call
651
652     self.check(['gen',
653                 '-c', 'debug_goma',
654                 '--swarming-targets-file', '/tmp/swarming_targets',
655                 '--isolate-map-file',
656                 '/fake_src/testing/buildbot/gn_isolate_map.pyl',
657                 '--isolate-map-file',
658                 '/fake_src/testing/buildbot/gn_isolate_map2.pyl',
659                 '//out/Default'], mbw=mbw, ret=0)
660     self.assertIn('/fake_src/out/Default/cc_perftests.isolate',
661                   mbw.files)
662     self.assertIn('/fake_src/out/Default/cc_perftests.isolated.gen.json',
663                   mbw.files)
664
665
666   def test_duplicate_isolate_maps(self):
667     files = {
668         '/tmp/swarming_targets':
669         'cc_perftests\n',
670         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
671         ("{'cc_perftests': {"
672          "  'label': '//cc:cc_perftests',"
673          "  'type': 'console_test_launcher',"
674          "}}\n"),
675         '/fake_src/testing/buildbot/gn_isolate_map2.pyl':
676         ("{'cc_perftests': {"
677          "  'label': '//cc:cc_perftests',"
678          "  'type': 'console_test_launcher',"
679          "}}\n"),
680         'c:\\fake_src\out\Default\cc_perftests.exe.runtime_deps':
681         ("cc_perftests\n"),
682     }
683     mbw = self.fake_mbw(files=files, win32=True)
684     # Check that passing duplicate targets into mb fails.
685     self.check(['gen',
686                 '-c', 'debug_goma',
687                 '--swarming-targets-file', '/tmp/swarming_targets',
688                 '--isolate-map-file',
689                 '/fake_src/testing/buildbot/gn_isolate_map.pyl',
690                 '--isolate-map-file',
691                 '/fake_src/testing/buildbot/gn_isolate_map2.pyl',
692                 '//out/Default'], mbw=mbw, ret=1)
693
694
695   def test_isolate(self):
696     files = {
697         '/fake_src/out/Default/toolchain.ninja':
698         "",
699         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
700         ("{'base_unittests': {"
701          "  'label': '//base:base_unittests',"
702          "  'type': 'console_test_launcher',"
703          "}}\n"),
704         '/fake_src/out/Default/base_unittests.runtime_deps':
705         ("base_unittests\n"),
706     }
707     self.check(['isolate', '-c', 'debug_goma', '//out/Default',
708                 'base_unittests'], files=files, ret=0)
709
710     # test running isolate on an existing build_dir
711     files['/fake_src/out/Default/args.gn'] = 'is_debug = true\n'
712     self.check(['isolate', '//out/Default', 'base_unittests'],
713                files=files, ret=0)
714
715     self.check(['isolate', '//out/Default', 'base_unittests'],
716                files=files, ret=0)
717
718     # Existing build dir that uses a .gni import.
719     files['/fake_src/out/Default/args.gn'] = 'import("//import/args.gni")\n'
720     files['/fake_src/import/args.gni'] = 'is_debug = true\n'
721     self.check(['isolate', '//out/Default', 'base_unittests'],
722                files=files,
723                ret=0)
724
725   def test_dedup_runtime_deps(self):
726     files = {
727         '/tmp/swarming_targets':
728         'base_unittests\n',
729         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
730         ("{'base_unittests': {"
731          "  'label': '//base:base_unittests',"
732          "  'type': 'console_test_launcher',"
733          "}}\n"),
734     }
735
736     mbw = self.fake_mbw(files)
737
738     def fake_call(cmd, env=None, capture_output=True, input=''):
739       del cmd
740       del env
741       del capture_output
742       del input
743       mbw.files['/fake_src/out/Default/base_unittests.runtime_deps'] = (
744           'base_unittests\n'
745           '../../filters/some_filter/\n'
746           '../../filters/some_filter/foo\n'
747           '../../filters/another_filter/hoo\n')
748       return 0, '', ''
749
750     mbw.Call = fake_call
751
752     self.check([
753         'gen', '-c', 'debug_goma', '--swarming-targets-file',
754         '/tmp/swarming_targets', '//out/Default'
755     ],
756                mbw=mbw,
757                ret=0)
758     self.assertIn('/fake_src/out/Default/base_unittests.isolate', mbw.files)
759     files = mbw.files.get('/fake_src/out/Default/base_unittests.isolate')
760     self.assertIn('../../filters/some_filter', files)
761     self.assertNotIn('../../filters/some_filter/foo', files)
762     self.assertIn('../../filters/another_filter/hoo', files)
763
764   def test_isolate_dir(self):
765     files = {
766         '/fake_src/out/Default/toolchain.ninja':
767         "",
768         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
769         ("{'base_unittests': {"
770          "  'label': '//base:base_unittests',"
771          "  'type': 'console_test_launcher',"
772          "}}\n"),
773     }
774     mbw = self.fake_mbw(files=files)
775     mbw.cmds.append((0, '', ''))  # Result of `gn gen`
776     mbw.cmds.append((0, '', ''))  # Result of `autoninja`
777
778     # Result of `gn desc runtime_deps`
779     mbw.cmds.append((0, 'base_unitests\n../../test_data/\n', ''))
780     self.check(['isolate', '-c', 'debug_goma', '//out/Default',
781                 'base_unittests'], mbw=mbw, ret=0, err='')
782
783   def test_isolate_generated_dir(self):
784     files = {
785         '/fake_src/out/Default/toolchain.ninja':
786         "",
787         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
788         ("{'base_unittests': {"
789          "  'label': '//base:base_unittests',"
790          "  'type': 'console_test_launcher',"
791          "}}\n"),
792     }
793     mbw = self.fake_mbw(files=files)
794     mbw.cmds.append((0, '', ''))  # Result of `gn gen`
795     mbw.cmds.append((0, '', ''))  # Result of `autoninja`
796
797     # Result of `gn desc runtime_deps`
798     mbw.cmds.append((0, 'base_unitests\ntest_data/\n', ''))
799     expected_err = ('error: gn `data` items may not list generated directories;'
800                     ' list files in directory instead for:\n'
801                     '//out/Default/test_data/\n')
802     self.check(['isolate', '-c', 'debug_goma', '//out/Default',
803                 'base_unittests'], mbw=mbw, ret=1)
804     self.assertEqual(mbw.out[-len(expected_err):], expected_err)
805
806
807   def test_run(self):
808     files = {
809         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
810         ("{'base_unittests': {"
811          "  'label': '//base:base_unittests',"
812          "  'type': 'console_test_launcher',"
813          "}}\n"),
814         '/fake_src/out/Default/base_unittests.runtime_deps':
815         ("base_unittests\n"),
816     }
817     mbw = self.check(['run', '-c', 'debug_goma', '//out/Default',
818                      'base_unittests'], files=files, ret=0)
819     # pylint: disable=line-too-long
820     self.assertEqual(
821         mbw.files['/fake_src/out/Default/base_unittests.isolate'],
822           '{"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')
823     # pylint: enable=line-too-long
824
825   def test_run_swarmed(self):
826     files = {
827         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
828         ("{'base_unittests': {"
829          "  'label': '//base:base_unittests',"
830          "  'type': 'console_test_launcher',"
831          "}}\n"),
832         '/fake_src/out/Default/base_unittests.runtime_deps':
833         ("base_unittests\n"),
834         '/fake_src/out/Default/base_unittests.archive.json':
835         ("{\"base_unittests\":\"fake_hash\"}"),
836         '/fake_src/third_party/depot_tools/cipd_manifest.txt':
837         ("# vpython\n"
838          "/some/vpython/pkg  git_revision:deadbeef\n"),
839     }
840
841     task_json = json.dumps({'tasks': [{'task_id': '00000'}]})
842     collect_json = json.dumps({'00000': {'results': {}}})
843
844     mbw = self.fake_mbw(files=files)
845     mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
846     mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
847     original_impl = mbw.ToSrcRelPath
848
849     def to_src_rel_path_stub(path):
850       if path.endswith('base_unittests.archive.json'):
851         return 'base_unittests.archive.json'
852       return original_impl(path)
853
854     mbw.ToSrcRelPath = to_src_rel_path_stub
855
856     self.check(['run', '-s', '-c', 'debug_goma', '//out/Default',
857                 'base_unittests'], mbw=mbw, ret=0)
858
859     # Specify a custom dimension via '-d'.
860     mbw = self.fake_mbw(files=files)
861     mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
862     mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
863     mbw.ToSrcRelPath = to_src_rel_path_stub
864     self.check(['run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7',
865                 '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
866
867     # Use the internal swarming server via '--internal'.
868     mbw = self.fake_mbw(files=files)
869     mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
870     mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
871     mbw.ToSrcRelPath = to_src_rel_path_stub
872     self.check([
873         'run', '-s', '--internal', '-c', 'debug_goma', '//out/Default',
874         'base_unittests'
875     ],
876                mbw=mbw,
877                ret=0)
878
879   def test_run_swarmed_task_failure(self):
880     files = {
881         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
882         ("{'base_unittests': {"
883          "  'label': '//base:base_unittests',"
884          "  'type': 'console_test_launcher',"
885          "}}\n"),
886         '/fake_src/out/Default/base_unittests.runtime_deps':
887         ("base_unittests\n"),
888         '/fake_src/out/Default/base_unittests.archive.json':
889         ("{\"base_unittests\":\"fake_hash\"}"),
890         '/fake_src/third_party/depot_tools/cipd_manifest.txt':
891         ("# vpython\n"
892          "/some/vpython/pkg  git_revision:deadbeef\n"),
893     }
894
895     task_json = json.dumps({'tasks': [{'task_id': '00000'}]})
896     collect_json = json.dumps({'00000': {'results': {'exit_code': 1}}})
897
898     mbw = self.fake_mbw(files=files)
899     mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
900     mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
901     original_impl = mbw.ToSrcRelPath
902
903     def to_src_rel_path_stub(path):
904       if path.endswith('base_unittests.archive.json'):
905         return 'base_unittests.archive.json'
906       return original_impl(path)
907
908     mbw.ToSrcRelPath = to_src_rel_path_stub
909
910     self.check(
911         ['run', '-s', '-c', 'debug_goma', '//out/Default', 'base_unittests'],
912         mbw=mbw,
913         ret=1)
914     mbw = self.fake_mbw(files=files)
915     mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json
916     mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json
917     mbw.ToSrcRelPath = to_src_rel_path_stub
918     self.check([
919         'run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7', '//out/Default',
920         'base_unittests'
921     ],
922                mbw=mbw,
923                ret=1)
924
925   def test_lookup(self):
926     self.check(['lookup', '-c', 'debug_goma'], ret=0,
927                out=('\n'
928                     'Writing """\\\n'
929                     'is_debug = true\n'
930                     'use_goma = true\n'
931                     '""" to _path_/args.gn.\n\n'
932                     '/fake_src/buildtools/linux64/gn gen _path_\n'))
933
934   def gen_starlark_gn_args_mbw(self, gn_args_json):
935     files = {
936         self.path('/fake_src/tools/mb/mb_config.pyl'):
937         CONFIG_STARLARK_GN_ARGS,
938         self.path('/fake_src/tools/mb/../../infra/config/generated/builders/'
939                   'gn_args_locations.json'):
940         TEST_GN_ARGS_LOCATIONS_JSON,
941         self.path('/fake_src/tools/mb/../../infra/config/generated/builders/'
942                   'ci/linux-official/gn-args.json'):
943         gn_args_json,
944     }
945     return self.fake_mbw(files=files, win32=is_win())
946
947   def test_lookup_starlark_gn_args(self):
948     mbw = self.gen_starlark_gn_args_mbw(TEST_GN_ARGS_JSON)
949     expected_out = ('\n'
950                     'Writing """\\\n'
951                     'bool_arg_lower_case = true\n'
952                     'string_arg = "has double quotes"\n'
953                     '""" to _path_/args.gn.\n\n')
954     if sys.platform == 'win32':
955       expected_out += 'c:\\fake_src\\buildtools\\win\\gn.exe gen _path_\n'
956     else:
957       expected_out += '/fake_src/buildtools/linux64/gn gen _path_\n'
958     self.check(['lookup', '-m', 'chromium', '-b', 'linux-official'],
959                mbw=mbw,
960                ret=0,
961                out=expected_out)
962
963   def test_lookup_starlark_gn_args_specified_phase(self):
964     mbw = self.gen_starlark_gn_args_mbw(TEST_GN_ARGS_JSON)
965     self.check([
966         'lookup', '-m', 'chromium', '-b', 'linux-official', '--phase', 'phase_1'
967     ],
968                mbw=mbw,
969                ret=1)
970     self.assertIn(
971         'MBErr: Must not specify a build --phase '
972         'for linux-official on chromium', mbw.out)
973
974   def test_lookup_starlark_phased_gn_args(self):
975     mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
976     expected_out = ('\n'
977                     'Writing """\\\n'
978                     'bool_arg_lower_case = false\n'
979                     'string_arg = "second phase"\n'
980                     '""" to _path_/args.gn.\n\n')
981     if sys.platform == 'win32':
982       expected_out += 'c:\\fake_src\\buildtools\\win\\gn.exe gen _path_\n'
983     else:
984       expected_out += '/fake_src/buildtools/linux64/gn gen _path_\n'
985     self.check([
986         'lookup', '-m', 'chromium', '-b', 'linux-official', '--phase', 'phase_2'
987     ],
988                mbw=mbw,
989                ret=0,
990                out=expected_out)
991
992   def test_lookup_starlark_phased_gn_args_no_phase(self):
993     mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
994     self.check(['lookup', '-m', 'chromium', '-b', 'linux-official'],
995                mbw=mbw,
996                ret=1)
997     self.assertIn(
998         'MBErr: Must specify a build --phase for linux-official on chromium',
999         mbw.out)
1000
1001   def test_lookup_starlark_phased_gn_args_wrong_phase(self):
1002     mbw = self.gen_starlark_gn_args_mbw(TEST_PHASED_GN_ARGS_JSON)
1003     self.check([
1004         'lookup', '-m', 'chromium', '-b', 'linux-official', '--phase', 'phase_3'
1005     ],
1006                mbw=mbw,
1007                ret=1)
1008     self.assertIn(
1009         'MBErr: Phase phase_3 doesn\'t exist for linux-official on chromium',
1010         mbw.out)
1011
1012   def test_lookup_gn_args_with_non_existent_gn_args_location_file(self):
1013     files = {
1014         self.path('/fake_src/tools/mb/mb_config.pyl'):
1015         textwrap.dedent("""\
1016             {
1017               'gn_args_locations_files': [
1018                 '../../infra/config/generated/builders/gn_args_locations.json',
1019               ],
1020               'builder_groups': {
1021                 'fake-group': {
1022                   'fake-builder': 'fake-config',
1023                 },
1024               },
1025               'configs': {
1026                 'fake-config': [],
1027               },
1028               'mixins': {},
1029             }
1030         """)
1031     }
1032     mbw = self.fake_mbw(files=files, win32=is_win())
1033     self.check(['lookup', '-m', 'fake-group', '-b', 'fake-builder'],
1034                mbw=mbw,
1035                ret=0)
1036
1037   def test_quiet_lookup(self):
1038     self.check(['lookup', '-c', 'debug_goma', '--quiet'], ret=0,
1039                out=('is_debug = true\n'
1040                     'use_goma = true\n'))
1041
1042   def test_lookup_goma_dir_expansion(self):
1043     self.check(['lookup', '-c', 'rel_bot', '-g', '/foo'],
1044                ret=0,
1045                out=('\n'
1046                     'Writing """\\\n'
1047                     'dcheck_always_on = false\n'
1048                     'enable_doom_melon = true\n'
1049                     'goma_dir = "/foo"\n'
1050                     'is_debug = false\n'
1051                     'use_goma = true\n'
1052                     '""" to _path_/args.gn.\n\n'
1053                     '/fake_src/buildtools/linux64/gn gen _path_\n'))
1054
1055   def test_help(self):
1056     orig_stdout = sys.stdout
1057     try:
1058       sys.stdout = io.StringIO()
1059       self.assertRaises(SystemExit, self.check, ['-h'])
1060       self.assertRaises(SystemExit, self.check, ['help'])
1061       self.assertRaises(SystemExit, self.check, ['help', 'gen'])
1062     finally:
1063       sys.stdout = orig_stdout
1064
1065   def test_multiple_phases(self):
1066     # Check that not passing a --phase to a multi-phase builder fails.
1067     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
1068                       'fake_multi_phase'], ret=1)
1069     self.assertIn('Must specify a build --phase', mbw.out)
1070
1071     # Check that passing a --phase to a single-phase builder fails.
1072     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
1073                       'fake_builder', '--phase', 'phase_1'], ret=1)
1074     self.assertIn('Must not specify a build --phase', mbw.out)
1075
1076     # Check that passing a wrong phase key to a multi-phase builder fails.
1077     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
1078                       'fake_multi_phase', '--phase', 'wrong_phase'], ret=1)
1079     self.assertIn('Phase wrong_phase doesn\'t exist', mbw.out)
1080
1081     # Check that passing a correct phase key to a multi-phase builder passes.
1082     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
1083                       'fake_multi_phase', '--phase', 'phase_1'], ret=0)
1084     self.assertIn('phase = 1', mbw.out)
1085
1086     mbw = self.check(['lookup', '-m', 'fake_builder_group', '-b',
1087                       'fake_multi_phase', '--phase', 'phase_2'], ret=0)
1088     self.assertIn('phase = 2', mbw.out)
1089
1090   def test_recursive_lookup(self):
1091     files = {
1092         '/fake_src/build/args/fake.gn': (
1093           'enable_doom_melon = true\n'
1094           'enable_antidoom_banana = true\n'
1095         )
1096     }
1097     self.check([
1098         'lookup', '-m', 'fake_builder_group', '-b', 'fake_args_file',
1099         '--recursive'
1100     ],
1101                files=files,
1102                ret=0,
1103                out=('dcheck_always_on = false\n'
1104                     'is_debug = false\n'
1105                     'use_goma = true\n'))
1106
1107   def test_train(self):
1108     mbw = self.fake_mbw()
1109     temp_dir = mbw.TempDir()
1110     self.check(['train', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
1111     self.assertIn(os.path.join(temp_dir, 'fake_builder_group.json'), mbw.files)
1112
1113   def test_validate(self):
1114     mbw = self.fake_mbw()
1115     self.check(['validate'], mbw=mbw, ret=0)
1116
1117   def test_bad_validate(self):
1118     mbw = self.fake_mbw()
1119     mbw.files[mbw.default_config] = TEST_BAD_CONFIG
1120     self.check(['validate', '-f', mbw.default_config], mbw=mbw, ret=1)
1121
1122   def test_duplicate_validate(self):
1123     mbw = self.fake_mbw()
1124     mbw.files[mbw.default_config] = TEST_DUP_CONFIG
1125     self.check(['validate'], mbw=mbw, ret=1)
1126     self.assertIn(
1127         'Duplicate configs detected. When evaluated fully, the '
1128         'following configs are all equivalent: \'some_config\', '
1129         '\'some_other_config\'.', mbw.out)
1130
1131   def test_good_expectations_validate(self):
1132     mbw = self.fake_mbw()
1133     # Train the expectations normally.
1134     temp_dir = mbw.TempDir()
1135     self.check(['train', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
1136     # Immediately validating them should pass.
1137     self.check(['validate', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
1138
1139   def test_bad_expectations_validate(self):
1140     mbw = self.fake_mbw()
1141     # Train the expectations normally.
1142     temp_dir = mbw.TempDir()
1143     self.check(['train', '--expectations-dir', temp_dir], mbw=mbw, ret=0)
1144     # Remove one of the expectation files.
1145     mbw.files.pop(os.path.join(temp_dir, 'fake_builder_group.json'))
1146     # Now validating should fail.
1147     self.check(['validate', '--expectations-dir', temp_dir], mbw=mbw, ret=1)
1148     self.assertIn('Expectations out of date', mbw.out)
1149
1150   def test_build_command_unix(self):
1151     files = {
1152         '/fake_src/out/Default/toolchain.ninja':
1153         '',
1154         '/fake_src/testing/buildbot/gn_isolate_map.pyl':
1155         ('{"base_unittests": {'
1156          '  "label": "//base:base_unittests",'
1157          '  "type": "console_test_launcher",'
1158          '  "args": [],'
1159          '}}\n')
1160     }
1161
1162     mbw = self.fake_mbw(files)
1163     self.check(['run', '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
1164     self.assertIn(['autoninja', '-C', 'out/Default', 'base_unittests'],
1165                   mbw.calls)
1166
1167   def test_build_command_windows(self):
1168     files = {
1169         'c:\\fake_src\\out\\Default\\toolchain.ninja':
1170         '',
1171         'c:\\fake_src\\testing\\buildbot\\gn_isolate_map.pyl':
1172         ('{"base_unittests": {'
1173          '  "label": "//base:base_unittests",'
1174          '  "type": "console_test_launcher",'
1175          '  "args": [],'
1176          '}}\n')
1177     }
1178
1179     mbw = self.fake_mbw(files, True)
1180     self.check(['run', '//out/Default', 'base_unittests'], mbw=mbw, ret=0)
1181     self.assertIn(['autoninja.bat', '-C', 'out\\Default', 'base_unittests'],
1182                   mbw.calls)
1183
1184   def test_ios_error_config_with_ios_json(self):
1185     """Ensures that ios_error config finds the correct iOS JSON file for args"""
1186     files = {
1187         '/fake_src/ios/build/bots/fake_builder_group/fake_ios_error.json':
1188         ('{"gn_args": ["is_debug=true"]}\n')
1189     }
1190     mbw = self.fake_mbw(files)
1191     self.check(['lookup', '-m', 'fake_builder_group', '-b', 'fake_ios_error'],
1192                mbw=mbw,
1193                ret=0,
1194                out=('\n'
1195                     'Writing """\\\n'
1196                     'is_debug = true\n'
1197                     '""" to _path_/args.gn.\n\n'
1198                     '/fake_src/buildtools/linux64/gn gen _path_\n'))
1199
1200   def test_bot_definition_in_ios_json_only(self):
1201     """Ensures that logic checks iOS JSON file for args
1202
1203     When builder definition is not present, ensure that ios/build/bots/ is
1204     checked.
1205     """
1206     files = {
1207         '/fake_src/ios/build/bots/fake_builder_group/fake_ios_bot.json':
1208         ('{"gn_args": ["is_debug=true"]}\n')
1209     }
1210     mbw = self.fake_mbw(files)
1211     self.check(['lookup', '-m', 'fake_builder_group', '-b', 'fake_ios_bot'],
1212                mbw=mbw,
1213                ret=0,
1214                out=('\n'
1215                     'Writing """\\\n'
1216                     'is_debug = true\n'
1217                     '""" to _path_/args.gn.\n\n'
1218                     '/fake_src/buildtools/linux64/gn gen _path_\n'))
1219
1220   def test_ios_error_config_missing_json_definition(self):
1221     """Ensures MBErr is thrown
1222
1223     Expect MBErr with 'No iOS definition ...' for iOS bots when the bot config
1224     is ios_error, but there is no iOS JSON definition for it.
1225     """
1226     mbw = self.fake_mbw()
1227     self.check(['lookup', '-m', 'fake_builder_group', '-b', 'fake_ios_error'],
1228                mbw=mbw,
1229                ret=1)
1230     self.assertIn('MBErr: No iOS definition was found.', mbw.out)
1231
1232   def test_bot_missing_definition(self):
1233     """Ensures builder missing MBErr is thrown
1234
1235     Expect the original MBErr to be thrown for iOS bots when the bot definition
1236     doesn't exist at all.
1237     """
1238     mbw = self.fake_mbw()
1239     self.check(['lookup', '-m', 'fake_builder_group', '-b', 'random_bot'],
1240                mbw=mbw,
1241                ret=1)
1242     self.assertIn('MBErr: Builder name "random_bot"  not found under groups',
1243                   mbw.out)
1244
1245
1246 if __name__ == '__main__':
1247   unittest.main()