autoninja_test.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #!/usr/bin/env vpython3
  2. # Copyright (c) 2022 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. import glob
  6. import multiprocessing
  7. import os
  8. import os.path
  9. import io
  10. import sys
  11. import unittest
  12. import contextlib
  13. from unittest import mock
  14. from parameterized import parameterized
  15. ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  16. sys.path.insert(0, ROOT_DIR)
  17. import autoninja
  18. from testing_support import trial_dir
  19. def write(filename, content):
  20. """Writes the content of a file and create the directories as needed."""
  21. filename = os.path.abspath(filename)
  22. dirname = os.path.dirname(filename)
  23. if not os.path.isdir(dirname):
  24. os.makedirs(dirname)
  25. with open(filename, 'w') as f:
  26. f.write(content)
  27. class AutoninjaTest(trial_dir.TestCase):
  28. def setUp(self):
  29. super(AutoninjaTest, self).setUp()
  30. self.previous_dir = os.getcwd()
  31. os.chdir(self.root_dir)
  32. def tearDown(self):
  33. os.chdir(self.previous_dir)
  34. super(AutoninjaTest, self).tearDown()
  35. def test_autoninja(self):
  36. """Test that by default (= no GN args) autoninja delegates to ninja."""
  37. with mock.patch('ninja.main', return_value=0) as ninja_main:
  38. out_dir = os.path.join('out', 'dir')
  39. write(os.path.join(out_dir, 'args.gn'), '')
  40. autoninja.main(['autoninja.py', '-C', out_dir])
  41. ninja_main.assert_called_once()
  42. args = ninja_main.call_args.args[0]
  43. self.assertIn('-C', args)
  44. self.assertEqual(args[args.index('-C') + 1], out_dir)
  45. @mock.patch('sys.platform', 'win32')
  46. def test_autoninja_splits_args_on_windows(self):
  47. """
  48. Test that autoninja correctly handles the special case of being
  49. passed its arguments as a quoted, whitespace-delimited string on
  50. Windows.
  51. """
  52. with mock.patch('ninja.main', return_value=0) as ninja_main:
  53. out_dir = os.path.join('out', 'dir')
  54. write(os.path.join(out_dir, 'args.gn'), '')
  55. autoninja.main([
  56. 'autoninja.py',
  57. '-C {} base'.format(out_dir),
  58. ])
  59. ninja_main.assert_called_once()
  60. args = ninja_main.call_args.args[0]
  61. self.assertIn('-C', args)
  62. self.assertEqual(args[args.index('-C') + 1], out_dir)
  63. self.assertIn('base', args)
  64. @mock.patch('sys.platform', 'linux')
  65. def test_autoninja_goma_not_supported_linux(self):
  66. """
  67. Test that when specifying use_goma=true and on linux, the
  68. message that goma is not supported is displayed.
  69. """
  70. goma_dir = os.path.join(self.root_dir, 'goma_dir')
  71. with mock.patch.dict(os.environ, {"GOMA_DIR": goma_dir}):
  72. out_dir = os.path.join('out', 'dir')
  73. write(os.path.join(out_dir, 'args.gn'), 'use_goma=true')
  74. write(
  75. os.path.join(
  76. 'goma_dir', 'gomacc.exe'
  77. if sys.platform.startswith('win') else 'gomacc'), 'content')
  78. with contextlib.redirect_stderr(io.StringIO()) as f:
  79. with self.assertRaises(SystemExit):
  80. self.assertEqual(
  81. autoninja.main(['autoninja.py', '-C', out_dir]), 1)
  82. self.assertIn(
  83. "The gn arg `use_goma=true` is no longer supported.",
  84. f.getvalue())
  85. @mock.patch('sys.platform', 'darwin')
  86. def test_autoninja_goma_not_supported_mac(self):
  87. """
  88. Test that when specifying use_goma=true and on mac, the
  89. message that goma is not supported is displayed.
  90. """
  91. goma_dir = os.path.join(self.root_dir, 'goma_dir')
  92. with mock.patch.dict(os.environ, {"GOMA_DIR": goma_dir}):
  93. out_dir = os.path.join('out', 'dir')
  94. write(os.path.join(out_dir, 'args.gn'), 'use_goma=true')
  95. write(
  96. os.path.join(
  97. 'goma_dir', 'gomacc.exe'
  98. if sys.platform.startswith('win') else 'gomacc'), 'content')
  99. with contextlib.redirect_stderr(io.StringIO()) as f:
  100. with self.assertRaises(SystemExit):
  101. self.assertEqual(
  102. autoninja.main(['autoninja.py', '-C', out_dir]), 1)
  103. self.assertIn(
  104. "The gn arg `use_goma=true` is no longer supported.",
  105. f.getvalue())
  106. @mock.patch('sys.platform', 'win')
  107. def test_autoninja_goma(self):
  108. """
  109. Test that when specifying use_goma=true, autoninja verifies that Goma
  110. is running and then delegates to ninja.
  111. """
  112. goma_dir = os.path.join(self.root_dir, 'goma_dir')
  113. with mock.patch('subprocess.call', return_value=0), \
  114. mock.patch('ninja.main', return_value=0) as ninja_main, \
  115. mock.patch.dict(os.environ, {"GOMA_DIR": goma_dir}):
  116. out_dir = os.path.join('out', 'dir')
  117. write(os.path.join(out_dir, 'args.gn'), 'use_goma=true')
  118. write(
  119. os.path.join(
  120. 'goma_dir', 'gomacc.exe'
  121. if sys.platform.startswith('win') else 'gomacc'), 'content')
  122. autoninja.main(['autoninja.py', '-C', out_dir])
  123. ninja_main.assert_called_once()
  124. args = ninja_main.call_args.args[0]
  125. self.assertIn('-C', args)
  126. self.assertEqual(args[args.index('-C') + 1], out_dir)
  127. # Check that autoninja correctly calculated the number of jobs to use
  128. # as required for remote execution, instead of using the value for
  129. # local execution.
  130. self.assertIn('-j', args)
  131. parallel_j = int(args[args.index('-j') + 1])
  132. self.assertGreater(parallel_j, multiprocessing.cpu_count() * 2)
  133. def test_autoninja_reclient(self):
  134. """
  135. Test that when specifying use_remoteexec=true, autoninja delegates to
  136. ninja_reclient.
  137. """
  138. with mock.patch('ninja_reclient.main',
  139. return_value=0) as ninja_reclient_main:
  140. out_dir = os.path.join('out', 'dir')
  141. write(os.path.join(out_dir, 'args.gn'), 'use_remoteexec=true')
  142. write(os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
  143. 'RBE_v=2')
  144. write(os.path.join('buildtools', 'reclient', 'version.txt'), '0.0')
  145. autoninja.main(['autoninja.py', '-C', out_dir])
  146. ninja_reclient_main.assert_called_once()
  147. args = ninja_reclient_main.call_args.args[0]
  148. self.assertIn('-C', args)
  149. self.assertEqual(args[args.index('-C') + 1], out_dir)
  150. # Check that autoninja correctly calculated the number of jobs to use
  151. # as required for remote execution, instead of using the value for
  152. # local execution.
  153. self.assertIn('-j', args)
  154. parallel_j = int(args[args.index('-j') + 1])
  155. self.assertGreater(parallel_j, multiprocessing.cpu_count() * 2)
  156. def test_autoninja_siso(self):
  157. """
  158. Test that when specifying use_siso=true, autoninja delegates to siso.
  159. """
  160. with mock.patch('siso.main', return_value=0) as siso_main:
  161. out_dir = os.path.join('out', 'dir')
  162. write(os.path.join(out_dir, 'args.gn'), 'use_siso=true')
  163. autoninja.main(['autoninja.py', '-C', out_dir])
  164. siso_main.assert_called_once()
  165. args = siso_main.call_args.args[0]
  166. self.assertIn('-C', args)
  167. self.assertEqual(args[args.index('-C') + 1], out_dir)
  168. def test_autoninja_siso_reclient(self):
  169. """
  170. Test that when specifying use_siso=true and use_remoteexec=true,
  171. autoninja delegates to autosiso.
  172. """
  173. with mock.patch('autosiso.main', return_value=0) as autosiso_main:
  174. out_dir = os.path.join('out', 'dir')
  175. write(os.path.join(out_dir, 'args.gn'),
  176. 'use_siso=true\nuse_remoteexec=true')
  177. write(os.path.join('buildtools', 'reclient_cfgs', 'reproxy.cfg'),
  178. 'RBE_v=2')
  179. write(os.path.join('buildtools', 'reclient', 'version.txt'), '0.0')
  180. autoninja.main(['autoninja.py', '-C', out_dir])
  181. autosiso_main.assert_called_once()
  182. args = autosiso_main.call_args.args[0]
  183. self.assertIn('-C', args)
  184. self.assertEqual(args[args.index('-C') + 1], out_dir)
  185. @parameterized.expand([
  186. ("non corp machine", False, None, None, False),
  187. ("non corp adc account", True, "foo@chromium.org", None, True),
  188. ("corp adc account", True, "foo@google.com", None, False),
  189. ("non corp gcloud auth account", True, None, "foo@chromium.org", True),
  190. ("corp gcloud auth account", True, None, "foo@google.com", False),
  191. ])
  192. def test_is_corp_machine_using_external_account(self, _, is_corp,
  193. adc_account,
  194. gcloud_auth_account,
  195. expected):
  196. for shelve_file in glob.glob(
  197. os.path.join(autoninja.SCRIPT_DIR, ".autoninja*")):
  198. # Clear cache.
  199. os.remove(shelve_file)
  200. with mock.patch('autoninja._is_google_corp_machine',
  201. return_value=is_corp), mock.patch(
  202. 'autoninja._adc_account',
  203. return_value=adc_account), mock.patch(
  204. 'autoninja._gcloud_auth_account',
  205. return_value=gcloud_auth_account):
  206. self.assertEqual(
  207. bool(
  208. # pylint: disable=line-too-long
  209. autoninja._is_google_corp_machine_using_external_account()),
  210. expected)
  211. def test_gn_lines(self):
  212. out_dir = os.path.join('out', 'dir')
  213. # Make sure nested import directives work. This is based on the
  214. # reclient test.
  215. write(os.path.join(out_dir, 'args.gn'), 'import("//out/common.gni")')
  216. write(os.path.join('out', 'common.gni'), 'import("common_2.gni")')
  217. write(os.path.join('out', 'common_2.gni'), 'use_remoteexec=true')
  218. lines = list(
  219. autoninja._gn_lines(out_dir, os.path.join(out_dir, 'args.gn')))
  220. # The test will only pass if both imports work and
  221. # 'use_remoteexec=true' is seen.
  222. self.assertListEqual(lines, [
  223. 'use_remoteexec=true',
  224. ])
  225. @mock.patch('sys.platform', 'win32')
  226. def test_print_cmd_windows(self):
  227. args = [
  228. 'C:\\Program Files\\Python 3\\bin\\python3.exe', 'ninja.py', '-C',
  229. 'out\\direc tory\\',
  230. '../../base/types/expected_macros_unittest.cc^', '-j', '140'
  231. ]
  232. with contextlib.redirect_stderr(io.StringIO()) as f:
  233. autoninja._print_cmd(args)
  234. self.assertEqual(
  235. f.getvalue(),
  236. '"C:\\Program Files\\Python 3\\bin\\python3.exe" ninja.py -C ' +
  237. '"out\\direc tory\\" ' +
  238. '../../base/types/expected_macros_unittest.cc^^ -j 140\n')
  239. @mock.patch('sys.platform', 'linux')
  240. def test_print_cmd_linux(self):
  241. args = [
  242. '/home/user name/bin/python3', 'ninja.py', '-C', 'out/direc tory/',
  243. '../../base/types/expected_macros_unittest.cc^', '-j', '140'
  244. ]
  245. with contextlib.redirect_stderr(io.StringIO()) as f:
  246. autoninja._print_cmd(args)
  247. self.assertEqual(
  248. f.getvalue(),
  249. "'/home/user name/bin/python3' ninja.py -C 'out/direc tory/' " +
  250. "'../../base/types/expected_macros_unittest.cc^' -j 140\n")
  251. if __name__ == '__main__':
  252. unittest.main()