scm_unittest.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/usr/bin/env vpython3
  2. # Copyright (c) 2012 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. """Unit tests for scm.py."""
  6. import logging
  7. import os
  8. import sys
  9. import tempfile
  10. import unittest
  11. from unittest import mock
  12. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  13. from testing_support import fake_repos
  14. import scm
  15. import subprocess
  16. import subprocess2
  17. def callError(code=1, cmd='', cwd='', stdout=b'', stderr=b''):
  18. return subprocess2.CalledProcessError(code, cmd, cwd, stdout, stderr)
  19. class GitWrapperTestCase(unittest.TestCase):
  20. def setUp(self):
  21. super(GitWrapperTestCase, self).setUp()
  22. self.root_dir = '/foo/bar'
  23. def testRefToRemoteRef(self):
  24. remote = 'origin'
  25. refs = {
  26. 'refs/branch-heads/1234': ('refs/remotes/branch-heads/', '1234'),
  27. # local refs for upstream branch
  28. 'refs/remotes/%s/foobar' % remote:
  29. ('refs/remotes/%s/' % remote, 'foobar'),
  30. '%s/foobar' % remote: ('refs/remotes/%s/' % remote, 'foobar'),
  31. # upstream ref for branch
  32. 'refs/heads/foobar': ('refs/remotes/%s/' % remote, 'foobar'),
  33. # could be either local or upstream ref, assumed to refer to
  34. # upstream, but probably don't want to encourage refs like this.
  35. 'heads/foobar': ('refs/remotes/%s/' % remote, 'foobar'),
  36. # underspecified, probably intended to refer to a local branch
  37. 'foobar':
  38. None,
  39. # tags and other refs
  40. 'refs/tags/TAG':
  41. None,
  42. 'refs/changes/34/1234':
  43. None,
  44. }
  45. for k, v in refs.items():
  46. r = scm.GIT.RefToRemoteRef(k, remote)
  47. self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v))
  48. def testRemoteRefToRef(self):
  49. remote = 'origin'
  50. refs = {
  51. 'refs/remotes/branch-heads/1234': 'refs/branch-heads/1234',
  52. # local refs for upstream branch
  53. 'refs/remotes/origin/foobar': 'refs/heads/foobar',
  54. # tags and other refs
  55. 'refs/tags/TAG': 'refs/tags/TAG',
  56. 'refs/changes/34/1234': 'refs/changes/34/1234',
  57. # different remote
  58. 'refs/remotes/other-remote/foobar': None,
  59. # underspecified, probably intended to refer to a local branch
  60. 'heads/foobar': None,
  61. 'origin/foobar': None,
  62. 'foobar': None,
  63. None: None,
  64. }
  65. for k, v in refs.items():
  66. r = scm.GIT.RemoteRefToRef(k, remote)
  67. self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v))
  68. @mock.patch('scm.GIT.Capture')
  69. @mock.patch('os.path.exists', lambda _: True)
  70. def testGetRemoteHeadRefLocal(self, mockCapture):
  71. mockCapture.side_effect = ['refs/remotes/origin/main']
  72. self.assertEqual(
  73. 'refs/remotes/origin/main',
  74. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  75. self.assertEqual(mockCapture.call_count, 1)
  76. @mock.patch('scm.GIT.Capture')
  77. @mock.patch('os.path.exists', lambda _: True)
  78. def testGetRemoteHeadRefLocalUpdateHead(self, mockCapture):
  79. mockCapture.side_effect = [
  80. 'refs/remotes/origin/master', # first symbolic-ref call
  81. 'foo', # set-head call
  82. 'refs/remotes/origin/main', # second symbolic-ref call
  83. ]
  84. self.assertEqual(
  85. 'refs/remotes/origin/main',
  86. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  87. self.assertEqual(mockCapture.call_count, 3)
  88. @mock.patch('scm.GIT.Capture')
  89. @mock.patch('os.path.exists', lambda _: True)
  90. def testGetRemoteHeadRefRemote(self, mockCapture):
  91. mockCapture.side_effect = [
  92. subprocess2.CalledProcessError(1, '', '', '', ''),
  93. subprocess2.CalledProcessError(1, '', '', '', ''),
  94. 'ref: refs/heads/main\tHEAD\n' +
  95. '0000000000000000000000000000000000000000\tHEAD',
  96. ]
  97. self.assertEqual(
  98. 'refs/remotes/origin/main',
  99. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  100. self.assertEqual(mockCapture.call_count, 3)
  101. @mock.patch('scm.GIT.Capture')
  102. def testIsVersioned(self, mockCapture):
  103. mockCapture.return_value = (
  104. '160000 blob 423dc77d2182cb2687c53598a1dcef62ea2804ae dir')
  105. actual_state = scm.GIT.IsVersioned('cwd', 'dir')
  106. self.assertEqual(actual_state, scm.VERSIONED_SUBMODULE)
  107. mockCapture.return_value = ''
  108. actual_state = scm.GIT.IsVersioned('cwd', 'dir')
  109. self.assertEqual(actual_state, scm.VERSIONED_NO)
  110. mockCapture.return_value = (
  111. '040000 tree ef016abffb316e47a02af447bc51342dcef6f3ca dir')
  112. actual_state = scm.GIT.IsVersioned('cwd', 'dir')
  113. self.assertEqual(actual_state, scm.VERSIONED_DIR)
  114. @mock.patch('os.path.exists', return_value=True)
  115. @mock.patch('scm.GIT.Capture')
  116. def testListSubmodules(self, mockCapture, *_mock):
  117. mockCapture.return_value = (
  118. 'submodule.submodulename.path foo/path/script'
  119. '\nsubmodule.submodule2name.path foo/path/script2')
  120. actual_list = scm.GIT.ListSubmodules('root')
  121. if sys.platform.startswith('win'):
  122. self.assertEqual(actual_list,
  123. ['foo\\path\\script', 'foo\\path\\script2'])
  124. else:
  125. self.assertEqual(actual_list,
  126. ['foo/path/script', 'foo/path/script2'])
  127. def testListSubmodules_missing(self):
  128. self.assertEqual(scm.GIT.ListSubmodules('root'), [])
  129. class RealGitTest(fake_repos.FakeReposTestBase):
  130. def setUp(self):
  131. super(RealGitTest, self).setUp()
  132. self.enabled = self.FAKE_REPOS.set_up_git()
  133. if self.enabled:
  134. self.cwd = scm.os.path.join(self.FAKE_REPOS.git_base, 'repo_1')
  135. else:
  136. self.skipTest('git fake repos not available')
  137. def testResolveCommit(self):
  138. with self.assertRaises(Exception):
  139. scm.GIT.ResolveCommit(self.cwd, 'zebra')
  140. with self.assertRaises(Exception):
  141. scm.GIT.ResolveCommit(self.cwd, 'r123456')
  142. first_rev = self.githash('repo_1', 1)
  143. self.assertEqual(first_rev, scm.GIT.ResolveCommit(self.cwd, first_rev))
  144. self.assertEqual(self.githash('repo_1', 2),
  145. scm.GIT.ResolveCommit(self.cwd, 'HEAD'))
  146. def testIsValidRevision(self):
  147. # Sha1's are [0-9a-z]{32}, so starting with a 'z' or 'r' should always
  148. # fail.
  149. self.assertFalse(scm.GIT.IsValidRevision(cwd=self.cwd, rev='zebra'))
  150. self.assertFalse(scm.GIT.IsValidRevision(cwd=self.cwd, rev='r123456'))
  151. # Valid cases
  152. first_rev = self.githash('repo_1', 1)
  153. self.assertTrue(scm.GIT.IsValidRevision(cwd=self.cwd, rev=first_rev))
  154. self.assertTrue(scm.GIT.IsValidRevision(cwd=self.cwd, rev='HEAD'))
  155. def testIsAncestor(self):
  156. self.assertTrue(
  157. scm.GIT.IsAncestor(self.githash('repo_1', 1),
  158. self.githash('repo_1', 2),
  159. cwd=self.cwd))
  160. self.assertFalse(
  161. scm.GIT.IsAncestor(self.githash('repo_1', 2),
  162. self.githash('repo_1', 1),
  163. cwd=self.cwd))
  164. self.assertFalse(scm.GIT.IsAncestor(self.githash('repo_1', 1), 'zebra'))
  165. def testGetAllFiles(self):
  166. self.assertEqual(['DEPS', 'foo bar', 'origin'],
  167. scm.GIT.GetAllFiles(self.cwd))
  168. def testGetSetConfig(self):
  169. key = 'scm.test-key'
  170. self.assertIsNone(scm.GIT.GetConfig(self.cwd, key))
  171. self.assertEqual('default-value',
  172. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  173. scm.GIT.SetConfig(self.cwd, key, 'set-value')
  174. self.assertEqual('set-value', scm.GIT.GetConfig(self.cwd, key))
  175. self.assertEqual('set-value',
  176. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  177. scm.GIT.SetConfig(self.cwd, key, '')
  178. self.assertEqual('', scm.GIT.GetConfig(self.cwd, key))
  179. self.assertEqual('', scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  180. scm.GIT._clear_config(self.cwd)
  181. subprocess.run(['git', 'config', key, 'line 1\nline 2\nline 3'],
  182. cwd=self.cwd)
  183. self.assertEqual('line 1\nline 2\nline 3',
  184. scm.GIT.GetConfig(self.cwd, key))
  185. self.assertEqual('line 1\nline 2\nline 3',
  186. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  187. scm.GIT.SetConfig(self.cwd, key)
  188. self.assertIsNone(scm.GIT.GetConfig(self.cwd, key))
  189. self.assertEqual('default-value',
  190. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  191. def testGetSetConfigBool(self):
  192. key = 'scm.test-key'
  193. self.assertFalse(scm.GIT.GetConfigBool(self.cwd, key))
  194. scm.GIT.SetConfig(self.cwd, key, 'true')
  195. self.assertTrue(scm.GIT.GetConfigBool(self.cwd, key))
  196. scm.GIT.SetConfig(self.cwd, key)
  197. self.assertFalse(scm.GIT.GetConfigBool(self.cwd, key))
  198. def testGetSetConfigList(self):
  199. key = 'scm.test-key'
  200. self.assertListEqual([], scm.GIT.GetConfigList(self.cwd, key))
  201. scm.GIT.SetConfig(self.cwd, key, 'foo')
  202. scm.GIT.Capture(['config', '--add', key, 'bar'], cwd=self.cwd)
  203. self.assertListEqual(['foo', 'bar'],
  204. scm.GIT.GetConfigList(self.cwd, key))
  205. scm.GIT.SetConfig(self.cwd, key, modify_all=True, value_pattern='^f')
  206. self.assertListEqual(['bar'], scm.GIT.GetConfigList(self.cwd, key))
  207. scm.GIT.SetConfig(self.cwd, key)
  208. self.assertListEqual([], scm.GIT.GetConfigList(self.cwd, key))
  209. def testYieldConfigRegexp(self):
  210. key1 = 'scm.aaa'
  211. key2 = 'scm.aaab'
  212. config = scm.GIT.YieldConfigRegexp(self.cwd, key1)
  213. with self.assertRaises(StopIteration):
  214. next(config)
  215. scm.GIT.SetConfig(self.cwd, key1, 'foo')
  216. scm.GIT.SetConfig(self.cwd, key2, 'bar')
  217. scm.GIT.Capture(['config', '--add', key2, 'baz'], cwd=self.cwd)
  218. config = scm.GIT.YieldConfigRegexp(self.cwd, '^scm\\.aaa')
  219. self.assertEqual((key1, 'foo'), next(config))
  220. self.assertEqual((key2, 'bar'), next(config))
  221. self.assertEqual((key2, 'baz'), next(config))
  222. with self.assertRaises(StopIteration):
  223. next(config)
  224. def testGetSetBranchConfig(self):
  225. branch = scm.GIT.GetBranch(self.cwd)
  226. key = 'scm.test-key'
  227. self.assertIsNone(scm.GIT.GetBranchConfig(self.cwd, branch, key))
  228. self.assertEqual(
  229. 'default-value',
  230. scm.GIT.GetBranchConfig(self.cwd, branch, key, 'default-value'))
  231. scm.GIT.SetBranchConfig(self.cwd, branch, key, 'set-value')
  232. self.assertEqual('set-value',
  233. scm.GIT.GetBranchConfig(self.cwd, branch, key))
  234. self.assertEqual(
  235. 'set-value',
  236. scm.GIT.GetBranchConfig(self.cwd, branch, key, 'default-value'))
  237. self.assertEqual(
  238. 'set-value',
  239. scm.GIT.GetConfig(self.cwd, 'branch.%s.%s' % (branch, key)))
  240. scm.GIT.SetBranchConfig(self.cwd, branch, key)
  241. self.assertIsNone(scm.GIT.GetBranchConfig(self.cwd, branch, key))
  242. def testFetchUpstreamTuple_NoUpstreamFound(self):
  243. self.assertEqual((None, None), scm.GIT.FetchUpstreamTuple(self.cwd))
  244. @mock.patch('scm.GIT.GetRemoteBranches', return_value=['origin/main'])
  245. def testFetchUpstreamTuple_GuessOriginMaster(self, _mockGetRemoteBranches):
  246. self.assertEqual(('origin', 'refs/heads/main'),
  247. scm.GIT.FetchUpstreamTuple(self.cwd))
  248. @mock.patch('scm.GIT.GetRemoteBranches',
  249. return_value=['origin/master', 'origin/main'])
  250. def testFetchUpstreamTuple_GuessOriginMain(self, _mockGetRemoteBranches):
  251. self.assertEqual(('origin', 'refs/heads/main'),
  252. scm.GIT.FetchUpstreamTuple(self.cwd))
  253. def testFetchUpstreamTuple_RietveldUpstreamConfig(self):
  254. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch',
  255. 'rietveld-upstream')
  256. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote',
  257. 'rietveld-remote')
  258. self.assertEqual(('rietveld-remote', 'rietveld-upstream'),
  259. scm.GIT.FetchUpstreamTuple(self.cwd))
  260. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch')
  261. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote')
  262. @mock.patch('scm.GIT.GetBranch', side_effect=callError())
  263. def testFetchUpstreamTuple_NotOnBranch(self, _mockGetBranch):
  264. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch',
  265. 'rietveld-upstream')
  266. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote',
  267. 'rietveld-remote')
  268. self.assertEqual(('rietveld-remote', 'rietveld-upstream'),
  269. scm.GIT.FetchUpstreamTuple(self.cwd))
  270. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch')
  271. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote')
  272. def testFetchUpstreamTuple_BranchConfig(self):
  273. branch = scm.GIT.GetBranch(self.cwd)
  274. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge', 'branch-merge')
  275. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote', 'branch-remote')
  276. self.assertEqual(('branch-remote', 'branch-merge'),
  277. scm.GIT.FetchUpstreamTuple(self.cwd))
  278. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge')
  279. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote')
  280. def testFetchUpstreamTuple_AnotherBranchConfig(self):
  281. branch = 'scm-test-branch'
  282. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge', 'other-merge')
  283. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote', 'other-remote')
  284. self.assertEqual(('other-remote', 'other-merge'),
  285. scm.GIT.FetchUpstreamTuple(self.cwd, branch))
  286. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge')
  287. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote')
  288. def testGetBranchRef(self):
  289. self.assertEqual('refs/heads/main', scm.GIT.GetBranchRef(self.cwd))
  290. HEAD = scm.GIT.Capture(['rev-parse', 'HEAD'], cwd=self.cwd)
  291. scm.GIT.Capture(['checkout', HEAD], cwd=self.cwd)
  292. self.assertIsNone(scm.GIT.GetBranchRef(self.cwd))
  293. scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
  294. def testGetBranch(self):
  295. self.assertEqual('main', scm.GIT.GetBranch(self.cwd))
  296. HEAD = scm.GIT.Capture(['rev-parse', 'HEAD'], cwd=self.cwd)
  297. scm.GIT.Capture(['checkout', HEAD], cwd=self.cwd)
  298. self.assertIsNone(scm.GIT.GetBranchRef(self.cwd))
  299. scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
  300. class DiffTestCase(unittest.TestCase):
  301. def setUp(self):
  302. self.root = tempfile.mkdtemp()
  303. os.makedirs(os.path.join(self.root, "foo", "dir"))
  304. with open(os.path.join(self.root, "foo", "file.txt"), "w") as f:
  305. f.write("foo\n")
  306. with open(os.path.join(self.root, "foo", "dir", "file.txt"), "w") as f:
  307. f.write("foo dir\n")
  308. os.makedirs(os.path.join(self.root, "baz_repo"))
  309. with open(os.path.join(self.root, "baz_repo", "file.txt"), "w") as f:
  310. f.write("baz\n")
  311. @mock.patch('scm.GIT.ListSubmodules')
  312. def testGetAllFiles_ReturnsAllFilesIfNoSubmodules(self, mockListSubmodules):
  313. mockListSubmodules.return_value = []
  314. files = scm.DIFF.GetAllFiles(self.root)
  315. if sys.platform.startswith('win'):
  316. self.assertCountEqual(
  317. files,
  318. ["foo\\file.txt", "foo\\dir\\file.txt", "baz_repo\\file.txt"])
  319. else:
  320. self.assertCountEqual(
  321. files,
  322. ["foo/file.txt", "foo/dir/file.txt", "baz_repo/file.txt"])
  323. @mock.patch('scm.GIT.ListSubmodules')
  324. def testGetAllFiles_IgnoresFilesInSubmodules(self, mockListSubmodules):
  325. mockListSubmodules.return_value = ['baz_repo']
  326. files = scm.DIFF.GetAllFiles(self.root)
  327. if sys.platform.startswith('win'):
  328. self.assertCountEqual(
  329. files, ["foo\\file.txt", "foo\\dir\\file.txt", "baz_repo"])
  330. else:
  331. self.assertCountEqual(
  332. files, ["foo/file.txt", "foo/dir/file.txt", "baz_repo"])
  333. if __name__ == '__main__':
  334. if '-v' in sys.argv:
  335. logging.basicConfig(level=logging.DEBUG)
  336. unittest.main()
  337. # vim: ts=2:sw=2:tw=80:et: