roll_dep_test.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. #!/usr/bin/env vpython3
  2. # Copyright (c) 2018 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 logging
  6. import os
  7. import sys
  8. import subprocess
  9. import unittest
  10. from unittest import mock
  11. ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  12. sys.path.insert(0, ROOT_DIR)
  13. import roll_dep
  14. from testing_support import fake_repos
  15. ROLL_DEP = os.path.join(ROOT_DIR, 'roll-dep')
  16. GCLIENT = os.path.join(ROOT_DIR, 'gclient')
  17. # TODO: Should fix these warnings.
  18. # pylint: disable=line-too-long
  19. def create_deps_content(git_base, path_to_revision_map):
  20. """
  21. Create a DEPS file content string with the given dependency mappings.
  22. Args:
  23. git_base: The base URL for git repositories
  24. path_to_revision_map: Dictionary mapping dependency paths to their revisions
  25. Returns:
  26. String with the complete DEPS file content including standard hooks
  27. """
  28. dep_lines = []
  29. git_base = git_base.replace('\\', '\\\\')
  30. for path, revision in path_to_revision_map.items():
  31. dep_lines.append(f' "{path}": "file://{git_base}repo_2@{revision}",')
  32. # Combine all parts with standard hooks.
  33. deps_content = [
  34. 'deps = {',
  35. '\n'.join(dep_lines),
  36. '}',
  37. 'hooks = [',
  38. ' {"action": ["foo", "--android", "{checkout_android}"]}',
  39. ']',
  40. ]
  41. return '\n'.join(deps_content)
  42. class FakeRepos(fake_repos.FakeReposBase):
  43. NB_GIT_REPOS = 2
  44. def populateGit(self):
  45. for x in range(1,4):
  46. self._commit_git('repo_2', {'origin': f'git/repo_2@{x}'})
  47. # repo_2@1 is the default revision.
  48. # Anything under 'third_party/not_supported' tests handling unsupported
  49. # cases.
  50. repo2_revision = self.git_hashes['repo_2'][1][0]
  51. self._commit_git(
  52. 'repo_1', {
  53. 'DEPS': create_deps_content(self.git_base, {
  54. 'src/foo': repo2_revision,
  55. 'src/third_party/repo_2/src': repo2_revision,
  56. 'src/third_party/repo_2B/src': repo2_revision,
  57. 'src/third_party/not_supported/with_divider/src': repo2_revision,
  58. 'src/third_party/not_supported/multiple_revisions/src': repo2_revision,
  59. 'src/third_party/not_supported/no_revision/src': repo2_revision
  60. }),
  61. 'README.chromium': '\n'.join([
  62. 'Name: test repo',
  63. 'URL: https://example.com',
  64. 'Version: 1.0',
  65. 'Revision: abcabc123123',
  66. 'License: MIT',
  67. ]),
  68. 'third_party/repo_2/README.chromium': '\n'.join([
  69. 'Name: test repo 2',
  70. 'URL: https://example.com',
  71. 'Version: 1.0',
  72. 'Revision: abc1234',
  73. 'License: MIT',
  74. ]),
  75. 'third_party/repo_2B/README.chromium': '\n'.join([
  76. 'Name: Override DEPS value for revision',
  77. 'URL: https://example.com',
  78. 'Version: 1.0',
  79. 'Revision: DEPS',
  80. 'License: MIT',
  81. ]),
  82. 'third_party/not_supported/with_divider/README.chromium': '\n'.join([
  83. 'Name: Deps divider not supported',
  84. 'URL: https://example.com',
  85. 'Version: 1.0',
  86. 'Revision: abc1234',
  87. 'License: MIT',
  88. '-------------------- DEPENDENCY DIVIDER --------------------',
  89. 'Name: So nothing here should change',
  90. 'URL: https://example.com',
  91. 'Version: 1.0',
  92. 'Revision: abc1234',
  93. 'License: MIT',
  94. ]),
  95. 'third_party/not_supported/multiple_revisions/README.chromium': '\n'.join([
  96. 'Name: Multiple revisions',
  97. 'URL: https://example.com',
  98. 'Version: 1.0',
  99. 'Revision: abc1234',
  100. 'License: MIT',
  101. 'Revision: abc1235', # This should not happen.
  102. ]),
  103. 'third_party/not_supported/no_revision/README.chromium': '\n'.join([
  104. 'Name: No revision',
  105. 'URL: https://example.com',
  106. 'Version: 1.0',
  107. 'License: MIT',
  108. ]),
  109. })
  110. class RollDepTest(fake_repos.FakeReposTestBase):
  111. FAKE_REPOS_CLASS = FakeRepos
  112. def setUp(self):
  113. super(RollDepTest, self).setUp()
  114. # Make sure it doesn't try to auto update when testing!
  115. self.env = os.environ.copy()
  116. self.env['DEPOT_TOOLS_UPDATE'] = '0'
  117. self.env['DEPOT_TOOLS_METRICS'] = '0'
  118. # Suppress Python 3 warnings and other test undesirables.
  119. self.env['GCLIENT_TEST'] = '1'
  120. self.maxDiff = None
  121. self.enabled = self.FAKE_REPOS.set_up_git()
  122. self.src_dir = os.path.join(self.root_dir, 'src')
  123. self.foo_dir = os.path.join(self.src_dir, 'foo')
  124. self.all_repos = [
  125. 'src/foo',
  126. 'src/third_party/repo_2/src',
  127. 'src/third_party/repo_2B/src',
  128. 'src/third_party/not_supported/with_divider/src',
  129. 'src/third_party/not_supported/multiple_revisions/src',
  130. 'src/third_party/not_supported/no_revision/src',
  131. ]
  132. if self.enabled:
  133. self.call([
  134. GCLIENT, 'config', 'file://' + self.git_base + 'repo_1',
  135. '--name', 'src'
  136. ],
  137. cwd=self.root_dir)
  138. self.call([GCLIENT, 'sync'], cwd=self.root_dir)
  139. def call(self, cmd, cwd=None):
  140. cwd = cwd or self.src_dir
  141. process = subprocess.Popen(cmd,
  142. cwd=cwd,
  143. stdout=subprocess.PIPE,
  144. stderr=subprocess.PIPE,
  145. env=self.env,
  146. shell=sys.platform.startswith('win'))
  147. stdout, stderr = process.communicate()
  148. logging.debug("XXX: %s\n%s\nXXX" % (' '.join(cmd), stdout))
  149. logging.debug("YYY: %s\n%s\nYYY" % (' '.join(cmd), stderr))
  150. stdout = stdout.decode('utf-8')
  151. stderr = stderr.decode('utf-8')
  152. return (stdout.replace('\r\n',
  153. '\n'), stderr.replace('\r\n',
  154. '\n'), process.returncode)
  155. def assert_deps_match(self, expected_path_to_revision_map):
  156. # Assume everything is at the default revision and only update the
  157. # provided paths.
  158. default_revision = self.githash('repo_2', 1)
  159. expected_map = {path: default_revision for path in self.all_repos}
  160. expected_map.update(expected_path_to_revision_map)
  161. for path, revision in expected_map.items():
  162. with self.subTest(path=path):
  163. path_dir = os.path.join(self.root_dir, path)
  164. self.assertEqual(self.gitrevparse(path_dir), revision)
  165. with open(os.path.join(self.src_dir, 'DEPS')) as f:
  166. actual_content = f.read()
  167. with self.subTest(path='DEPS'):
  168. expected_content = create_deps_content(self.git_base,expected_map)
  169. self.assertEqual(expected_content, actual_content)
  170. def testRollsDep(self):
  171. if not self.enabled:
  172. return
  173. stdout, stderr, returncode = self.call([ROLL_DEP]+self.all_repos)
  174. latest_revision = self.githash('repo_2', 3)
  175. self.assertEqual(stderr, '')
  176. self.assertEqual(returncode, 0)
  177. # All deps should be rolled to the latest revision.
  178. self.assert_deps_match({p: latest_revision for p in self.all_repos})
  179. commit_message = self.call(['git', 'log', '-n', '1'])[0]
  180. expected_message = 'Roll src/foo/ %s..%s (2 commits)' % (self.githash(
  181. 'repo_2', 1)[:9], latest_revision[:9])
  182. self.assertIn(expected_message, stdout)
  183. self.assertIn(expected_message, commit_message)
  184. def testRollsDepWithReadme(self):
  185. """Tests roll-dep when updating README.chromium files."""
  186. if not self.enabled:
  187. return
  188. stdout, stderr, returncode = self.call(
  189. [ROLL_DEP]+self.all_repos
  190. )
  191. latest_revision = self.githash('repo_2', 3)
  192. # All deps should be rolled to the latest revision (3).
  193. self.assert_deps_match({p: latest_revision for p in self.all_repos})
  194. self.assertEqual(stderr, '')
  195. self.assertEqual(returncode, 0)
  196. for path in self.all_repos:
  197. with self.subTest(path=path):
  198. contents = ''
  199. readme_path = os.path.join(self.root_dir, path, os.path.pardir, 'README.chromium')
  200. if os.path.exists(readme_path):
  201. with open(readme_path, 'r') as f:
  202. contents = f.read()
  203. if path == 'src/third_party/not_supported/no_revision/src':
  204. self.assertIn('README.chromium contains 0 Revision: lines', stdout)
  205. if 'not_supported' in path:
  206. self.assertNotIn(latest_revision, contents)
  207. continue
  208. # Check that the revision was updated.
  209. self.assertIn(f'Revision: {latest_revision}', contents)
  210. self.assertNotIn('Revision: abcabc123123', contents)
  211. self.assertNotIn('No README.chromium found', stdout)
  212. def testRollsDepReviewers(self):
  213. if not self.enabled:
  214. return
  215. stdout, stderr, returncode = self.call([
  216. ROLL_DEP, 'src/foo', '-r', 'foo@example.com', '-r',
  217. 'bar@example.com,baz@example.com'
  218. ])
  219. self.assertEqual(stderr, '')
  220. self.assertEqual(returncode, 0)
  221. expected_message = 'R=foo@example.com,bar@example.com,baz@example.com'
  222. self.assertIn(expected_message, stdout)
  223. def testRollsDepToSpecificRevision(self):
  224. if not self.enabled:
  225. return
  226. specified_revision = self.githash('repo_2', 2)
  227. stdout, stderr, returncode = self.call(
  228. [ROLL_DEP, 'src/foo', '--roll-to', specified_revision])
  229. self.assertEqual(stderr, '')
  230. self.assertEqual(returncode, 0)
  231. self.assert_deps_match({
  232. 'src/foo': specified_revision,
  233. })
  234. commit_message = self.call(['git', 'log', '-n', '1'])[0]
  235. expected_message = 'Roll src/foo/ %s..%s (1 commit)' % (self.githash(
  236. 'repo_2', 1)[:9], self.githash('repo_2', 2)[:9])
  237. self.assertIn(expected_message, stdout)
  238. self.assertIn(expected_message, commit_message)
  239. def testRollsDepLogLimit(self):
  240. if not self.enabled:
  241. return
  242. stdout, stderr, returncode = self.call(
  243. [ROLL_DEP, 'src/foo', '--log-limit', '1'])
  244. latest_revision = self.githash('repo_2', 3)
  245. self.assertEqual(stderr, '')
  246. self.assertEqual(returncode, 0)
  247. self.assert_deps_match({
  248. 'src/foo':latest_revision,
  249. })
  250. commit_message = self.call(['git', 'log', '-n', '1'])[0]
  251. expected_message = 'Roll src/foo/ %s..%s (2 commits)' % (self.githash(
  252. 'repo_2', 1)[:9], self.githash('repo_2', 3)[:9])
  253. self.assertIn(expected_message, stdout)
  254. self.assertIn(expected_message, commit_message)
  255. class CommitMessageTest(unittest.TestCase):
  256. def setUp(self):
  257. self.logs = '\n'.join([
  258. '2024-04-05 alice Goodbye',
  259. '2024-04-03 bob Hello World',
  260. ])
  261. # Mock the `git log` call.
  262. mock.patch('roll_dep.check_output', return_value=self.logs).start()
  263. self.addCleanup(mock.patch.stopall)
  264. def testShowShortLog(self):
  265. message = roll_dep.generate_commit_message(
  266. '/path/to/dir', 'dep', 'abc', 'def',
  267. 'https://chromium.googlesource.com', True, 10)
  268. self.assertIn('Roll dep/ abc..def (2 commits)', message)
  269. self.assertIn('$ git log', message)
  270. self.assertIn(self.logs, message)
  271. def testHideShortLog(self):
  272. message = roll_dep.generate_commit_message(
  273. '/path/to/dir', 'dep', 'abc', 'def',
  274. 'https://chromium.googlesource.com', False, 10)
  275. self.assertNotIn('$ git log', message)
  276. self.assertNotIn(self.logs, message)
  277. def testShouldShowLogWithPublicHost(self):
  278. self.assertTrue(
  279. roll_dep.should_show_log(
  280. 'https://chromium.googlesource.com/project'))
  281. def testShouldNotShowLogWithPrivateHost(self):
  282. self.assertFalse(
  283. roll_dep.should_show_log(
  284. 'https://private.googlesource.com/project'))
  285. if __name__ == '__main__':
  286. level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
  287. logging.basicConfig(level=level,
  288. format='%(asctime).19s %(levelname)s %(filename)s:'
  289. '%(lineno)s %(message)s')
  290. unittest.main()