mb_unittest.py 36 KB

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