scm_unittest.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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 unittest
  10. from unittest import mock
  11. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  12. from testing_support import fake_repos
  13. import scm
  14. import subprocess2
  15. def callError(code=1, cmd='', cwd='', stdout=b'', stderr=b''):
  16. return subprocess2.CalledProcessError(code, cmd, cwd, stdout, stderr)
  17. class GitWrapperTestCase(unittest.TestCase):
  18. def setUp(self):
  19. super(GitWrapperTestCase, self).setUp()
  20. self.root_dir = '/foo/bar'
  21. @mock.patch('scm.GIT.Capture')
  22. def testGetEmail(self, mockCapture):
  23. mockCapture.return_value = 'mini@me.com'
  24. self.assertEqual(scm.GIT.GetEmail(self.root_dir), 'mini@me.com')
  25. mockCapture.assert_called_with(['config', 'user.email'],
  26. cwd=self.root_dir)
  27. @mock.patch('scm.GIT.Capture')
  28. def testAssertVersion(self, mockCapture):
  29. cases = [
  30. ('1.7', True),
  31. ('1.7.9', True),
  32. ('1.7.9.foo-bar-baz', True),
  33. ('1.8', True),
  34. ('1.6.9', False),
  35. ]
  36. for expected_version, expected_ok in cases:
  37. class GIT(scm.GIT):
  38. pass
  39. mockCapture.return_value = 'git version ' + expected_version
  40. ok, version = GIT.AssertVersion('1.7')
  41. self.assertEqual(expected_ok, ok)
  42. self.assertEqual(expected_version, version)
  43. def testRefToRemoteRef(self):
  44. remote = 'origin'
  45. refs = {
  46. 'refs/branch-heads/1234': ('refs/remotes/branch-heads/', '1234'),
  47. # local refs for upstream branch
  48. 'refs/remotes/%s/foobar' % remote:
  49. ('refs/remotes/%s/' % remote, 'foobar'),
  50. '%s/foobar' % remote: ('refs/remotes/%s/' % remote, 'foobar'),
  51. # upstream ref for branch
  52. 'refs/heads/foobar': ('refs/remotes/%s/' % remote, 'foobar'),
  53. # could be either local or upstream ref, assumed to refer to
  54. # upstream, but probably don't want to encourage refs like this.
  55. 'heads/foobar': ('refs/remotes/%s/' % remote, 'foobar'),
  56. # underspecified, probably intended to refer to a local branch
  57. 'foobar':
  58. None,
  59. # tags and other refs
  60. 'refs/tags/TAG':
  61. None,
  62. 'refs/changes/34/1234':
  63. None,
  64. }
  65. for k, v in refs.items():
  66. r = scm.GIT.RefToRemoteRef(k, remote)
  67. self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v))
  68. def testRemoteRefToRef(self):
  69. remote = 'origin'
  70. refs = {
  71. 'refs/remotes/branch-heads/1234': 'refs/branch-heads/1234',
  72. # local refs for upstream branch
  73. 'refs/remotes/origin/foobar': 'refs/heads/foobar',
  74. # tags and other refs
  75. 'refs/tags/TAG': 'refs/tags/TAG',
  76. 'refs/changes/34/1234': 'refs/changes/34/1234',
  77. # different remote
  78. 'refs/remotes/other-remote/foobar': None,
  79. # underspecified, probably intended to refer to a local branch
  80. 'heads/foobar': None,
  81. 'origin/foobar': None,
  82. 'foobar': None,
  83. None: None,
  84. }
  85. for k, v in refs.items():
  86. r = scm.GIT.RemoteRefToRef(k, remote)
  87. self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v))
  88. @mock.patch('scm.GIT.Capture')
  89. @mock.patch('os.path.exists', lambda _: True)
  90. def testGetRemoteHeadRefLocal(self, mockCapture):
  91. mockCapture.side_effect = ['refs/remotes/origin/main']
  92. self.assertEqual(
  93. 'refs/remotes/origin/main',
  94. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  95. self.assertEqual(mockCapture.call_count, 1)
  96. @mock.patch('scm.GIT.Capture')
  97. @mock.patch('os.path.exists', lambda _: True)
  98. def testGetRemoteHeadRefLocalUpdateHead(self, mockCapture):
  99. mockCapture.side_effect = [
  100. 'refs/remotes/origin/master', # first symbolic-ref call
  101. 'foo', # set-head call
  102. 'refs/remotes/origin/main', # second symbolic-ref call
  103. ]
  104. self.assertEqual(
  105. 'refs/remotes/origin/main',
  106. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  107. self.assertEqual(mockCapture.call_count, 3)
  108. @mock.patch('scm.GIT.Capture')
  109. @mock.patch('os.path.exists', lambda _: True)
  110. def testGetRemoteHeadRefRemote(self, mockCapture):
  111. mockCapture.side_effect = [
  112. subprocess2.CalledProcessError(1, '', '', '', ''),
  113. 'ref: refs/heads/main\tHEAD\n' +
  114. '0000000000000000000000000000000000000000\tHEAD',
  115. ]
  116. self.assertEqual(
  117. 'refs/remotes/origin/main',
  118. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  119. self.assertEqual(mockCapture.call_count, 2)
  120. class RealGitTest(fake_repos.FakeReposTestBase):
  121. def setUp(self):
  122. super(RealGitTest, self).setUp()
  123. self.enabled = self.FAKE_REPOS.set_up_git()
  124. if self.enabled:
  125. self.cwd = scm.os.path.join(self.FAKE_REPOS.git_base, 'repo_1')
  126. else:
  127. self.skipTest('git fake repos not available')
  128. def testResolveCommit(self):
  129. self.assertIsNone(scm.GIT.ResolveCommit(self.cwd, 'zebra'))
  130. self.assertIsNone(scm.GIT.ResolveCommit(self.cwd, 'r123456'))
  131. first_rev = self.githash('repo_1', 1)
  132. self.assertEqual(first_rev, scm.GIT.ResolveCommit(self.cwd, first_rev))
  133. self.assertEqual(self.githash('repo_1', 2),
  134. scm.GIT.ResolveCommit(self.cwd, 'HEAD'))
  135. def testIsValidRevision(self):
  136. # Sha1's are [0-9a-z]{32}, so starting with a 'z' or 'r' should always
  137. # fail.
  138. self.assertFalse(scm.GIT.IsValidRevision(cwd=self.cwd, rev='zebra'))
  139. self.assertFalse(scm.GIT.IsValidRevision(cwd=self.cwd, rev='r123456'))
  140. # Valid cases
  141. first_rev = self.githash('repo_1', 1)
  142. self.assertTrue(scm.GIT.IsValidRevision(cwd=self.cwd, rev=first_rev))
  143. self.assertTrue(scm.GIT.IsValidRevision(cwd=self.cwd, rev='HEAD'))
  144. def testIsAncestor(self):
  145. self.assertTrue(
  146. scm.GIT.IsAncestor(self.githash('repo_1', 1),
  147. self.githash('repo_1', 2),
  148. cwd=self.cwd))
  149. self.assertFalse(
  150. scm.GIT.IsAncestor(self.githash('repo_1', 2),
  151. self.githash('repo_1', 1),
  152. cwd=self.cwd))
  153. self.assertFalse(scm.GIT.IsAncestor(self.githash('repo_1', 1), 'zebra'))
  154. def testGetAllFiles(self):
  155. self.assertEqual(['DEPS', 'foo bar', 'origin'],
  156. scm.GIT.GetAllFiles(self.cwd))
  157. def testGetSetConfig(self):
  158. key = 'scm.test-key'
  159. self.assertIsNone(scm.GIT.GetConfig(self.cwd, key))
  160. self.assertEqual('default-value',
  161. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  162. scm.GIT.SetConfig(self.cwd, key, 'set-value')
  163. self.assertEqual('set-value', scm.GIT.GetConfig(self.cwd, key))
  164. self.assertEqual('set-value',
  165. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  166. scm.GIT.SetConfig(self.cwd, key)
  167. self.assertIsNone(scm.GIT.GetConfig(self.cwd, key))
  168. self.assertEqual('default-value',
  169. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  170. def testGetSetBranchConfig(self):
  171. branch = scm.GIT.GetBranch(self.cwd)
  172. key = 'scm.test-key'
  173. self.assertIsNone(scm.GIT.GetBranchConfig(self.cwd, branch, key))
  174. self.assertEqual(
  175. 'default-value',
  176. scm.GIT.GetBranchConfig(self.cwd, branch, key, 'default-value'))
  177. scm.GIT.SetBranchConfig(self.cwd, branch, key, 'set-value')
  178. self.assertEqual('set-value',
  179. scm.GIT.GetBranchConfig(self.cwd, branch, key))
  180. self.assertEqual(
  181. 'set-value',
  182. scm.GIT.GetBranchConfig(self.cwd, branch, key, 'default-value'))
  183. self.assertEqual(
  184. 'set-value',
  185. scm.GIT.GetConfig(self.cwd, 'branch.%s.%s' % (branch, key)))
  186. scm.GIT.SetBranchConfig(self.cwd, branch, key)
  187. self.assertIsNone(scm.GIT.GetBranchConfig(self.cwd, branch, key))
  188. def testFetchUpstreamTuple_NoUpstreamFound(self):
  189. self.assertEqual((None, None), scm.GIT.FetchUpstreamTuple(self.cwd))
  190. @mock.patch('scm.GIT.GetRemoteBranches', return_value=['origin/main'])
  191. def testFetchUpstreamTuple_GuessOriginMaster(self, _mockGetRemoteBranches):
  192. self.assertEqual(('origin', 'refs/heads/main'),
  193. scm.GIT.FetchUpstreamTuple(self.cwd))
  194. @mock.patch('scm.GIT.GetRemoteBranches',
  195. return_value=['origin/master', 'origin/main'])
  196. def testFetchUpstreamTuple_GuessOriginMain(self, _mockGetRemoteBranches):
  197. self.assertEqual(('origin', 'refs/heads/main'),
  198. scm.GIT.FetchUpstreamTuple(self.cwd))
  199. def testFetchUpstreamTuple_RietveldUpstreamConfig(self):
  200. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch',
  201. 'rietveld-upstream')
  202. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote',
  203. 'rietveld-remote')
  204. self.assertEqual(('rietveld-remote', 'rietveld-upstream'),
  205. scm.GIT.FetchUpstreamTuple(self.cwd))
  206. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch')
  207. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote')
  208. @mock.patch('scm.GIT.GetBranch', side_effect=callError())
  209. def testFetchUpstreamTuple_NotOnBranch(self, _mockGetBranch):
  210. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch',
  211. 'rietveld-upstream')
  212. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote',
  213. 'rietveld-remote')
  214. self.assertEqual(('rietveld-remote', 'rietveld-upstream'),
  215. scm.GIT.FetchUpstreamTuple(self.cwd))
  216. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch')
  217. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote')
  218. def testFetchUpstreamTuple_BranchConfig(self):
  219. branch = scm.GIT.GetBranch(self.cwd)
  220. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge', 'branch-merge')
  221. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote', 'branch-remote')
  222. self.assertEqual(('branch-remote', 'branch-merge'),
  223. scm.GIT.FetchUpstreamTuple(self.cwd))
  224. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge')
  225. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote')
  226. def testFetchUpstreamTuple_AnotherBranchConfig(self):
  227. branch = 'scm-test-branch'
  228. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge', 'other-merge')
  229. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote', 'other-remote')
  230. self.assertEqual(('other-remote', 'other-merge'),
  231. scm.GIT.FetchUpstreamTuple(self.cwd, branch))
  232. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge')
  233. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote')
  234. def testGetBranchRef(self):
  235. self.assertEqual('refs/heads/main', scm.GIT.GetBranchRef(self.cwd))
  236. HEAD = scm.GIT.Capture(['rev-parse', 'HEAD'], cwd=self.cwd)
  237. scm.GIT.Capture(['checkout', HEAD], cwd=self.cwd)
  238. self.assertIsNone(scm.GIT.GetBranchRef(self.cwd))
  239. scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
  240. def testGetBranch(self):
  241. self.assertEqual('main', scm.GIT.GetBranch(self.cwd))
  242. HEAD = scm.GIT.Capture(['rev-parse', 'HEAD'], cwd=self.cwd)
  243. scm.GIT.Capture(['checkout', HEAD], cwd=self.cwd)
  244. self.assertIsNone(scm.GIT.GetBranchRef(self.cwd))
  245. scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
  246. if __name__ == '__main__':
  247. if '-v' in sys.argv:
  248. logging.basicConfig(level=logging.DEBUG)
  249. unittest.main()
  250. # vim: ts=2:sw=2:tw=80:et: