scm_unittest.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  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. from __future__ import annotations
  7. import logging
  8. import os
  9. import sys
  10. import tempfile
  11. import threading
  12. import unittest
  13. from unittest import mock
  14. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  15. from testing_support import fake_repos
  16. import scm
  17. import subprocess
  18. import subprocess2
  19. def callError(code=1, cmd='', cwd='', stdout=b'', stderr=b''):
  20. return subprocess2.CalledProcessError(code, cmd, cwd, stdout, stderr)
  21. class GitWrapperTestCase(unittest.TestCase):
  22. def setUp(self):
  23. super(GitWrapperTestCase, self).setUp()
  24. self.root_dir = '/foo/bar'
  25. def testRefToRemoteRef(self):
  26. remote = 'origin'
  27. refs = {
  28. 'refs/branch-heads/1234': ('refs/remotes/branch-heads/', '1234'),
  29. # local refs for upstream branch
  30. 'refs/remotes/%s/foobar' % remote:
  31. ('refs/remotes/%s/' % remote, 'foobar'),
  32. '%s/foobar' % remote: ('refs/remotes/%s/' % remote, 'foobar'),
  33. # upstream ref for branch
  34. 'refs/heads/foobar': ('refs/remotes/%s/' % remote, 'foobar'),
  35. # could be either local or upstream ref, assumed to refer to
  36. # upstream, but probably don't want to encourage refs like this.
  37. 'heads/foobar': ('refs/remotes/%s/' % remote, 'foobar'),
  38. # underspecified, probably intended to refer to a local branch
  39. 'foobar':
  40. None,
  41. # tags and other refs
  42. 'refs/tags/TAG':
  43. None,
  44. 'refs/changes/34/1234':
  45. None,
  46. }
  47. for k, v in refs.items():
  48. r = scm.GIT.RefToRemoteRef(k, remote)
  49. self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v))
  50. def testRemoteRefToRef(self):
  51. remote = 'origin'
  52. refs = {
  53. 'refs/remotes/branch-heads/1234': 'refs/branch-heads/1234',
  54. # local refs for upstream branch
  55. 'refs/remotes/origin/foobar': 'refs/heads/foobar',
  56. # tags and other refs
  57. 'refs/tags/TAG': 'refs/tags/TAG',
  58. 'refs/changes/34/1234': 'refs/changes/34/1234',
  59. # different remote
  60. 'refs/remotes/other-remote/foobar': None,
  61. # underspecified, probably intended to refer to a local branch
  62. 'heads/foobar': None,
  63. 'origin/foobar': None,
  64. 'foobar': None,
  65. None: None,
  66. }
  67. for k, v in refs.items():
  68. r = scm.GIT.RemoteRefToRef(k, remote)
  69. self.assertEqual(r, v, msg='%s -> %s, expected %s' % (k, r, v))
  70. @mock.patch('scm.GIT.Capture')
  71. @mock.patch('os.path.exists', lambda _: True)
  72. def testGetRemoteHeadRefLocal(self, mockCapture):
  73. mockCapture.side_effect = ['refs/remotes/origin/main']
  74. self.assertEqual(
  75. 'refs/remotes/origin/main',
  76. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  77. self.assertEqual(mockCapture.call_count, 1)
  78. @mock.patch('scm.GIT.Capture')
  79. @mock.patch('os.path.exists', lambda _: True)
  80. def testGetRemoteHeadRefLocalUpdateHead(self, mockCapture):
  81. mockCapture.side_effect = [
  82. 'refs/remotes/origin/master', # first symbolic-ref call
  83. 'foo', # set-head call
  84. 'refs/remotes/origin/main', # second symbolic-ref call
  85. ]
  86. self.assertEqual(
  87. 'refs/remotes/origin/main',
  88. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  89. self.assertEqual(mockCapture.call_count, 3)
  90. @mock.patch('scm.GIT.Capture')
  91. @mock.patch('os.path.exists', lambda _: True)
  92. def testGetRemoteHeadRefRemote(self, mockCapture):
  93. mockCapture.side_effect = [
  94. subprocess2.CalledProcessError(1, '', '', '', ''),
  95. subprocess2.CalledProcessError(1, '', '', '', ''),
  96. 'ref: refs/heads/main\tHEAD\n' +
  97. '0000000000000000000000000000000000000000\tHEAD',
  98. ]
  99. self.assertEqual(
  100. 'refs/remotes/origin/main',
  101. scm.GIT.GetRemoteHeadRef('foo', 'proto://url', 'origin'))
  102. self.assertEqual(mockCapture.call_count, 3)
  103. @mock.patch('scm.GIT.Capture')
  104. def testIsVersioned(self, mockCapture):
  105. mockCapture.return_value = (
  106. '160000 blob 423dc77d2182cb2687c53598a1dcef62ea2804ae dir')
  107. actual_state = scm.GIT.IsVersioned('cwd', 'dir')
  108. self.assertEqual(actual_state, scm.VERSIONED_SUBMODULE)
  109. mockCapture.return_value = ''
  110. actual_state = scm.GIT.IsVersioned('cwd', 'dir')
  111. self.assertEqual(actual_state, scm.VERSIONED_NO)
  112. mockCapture.return_value = (
  113. '040000 tree ef016abffb316e47a02af447bc51342dcef6f3ca dir')
  114. actual_state = scm.GIT.IsVersioned('cwd', 'dir')
  115. self.assertEqual(actual_state, scm.VERSIONED_DIR)
  116. @mock.patch('os.path.exists', return_value=True)
  117. @mock.patch('scm.GIT.Capture')
  118. def testListSubmodules(self, mockCapture, *_mock):
  119. mockCapture.return_value = (
  120. 'submodule.submodulename.path foo/path/script'
  121. '\nsubmodule.submodule2name.path foo/path/script2')
  122. actual_list = scm.GIT.ListSubmodules('root')
  123. if sys.platform.startswith('win'):
  124. self.assertEqual(actual_list,
  125. ['foo\\path\\script', 'foo\\path\\script2'])
  126. else:
  127. self.assertEqual(actual_list,
  128. ['foo/path/script', 'foo/path/script2'])
  129. def testListSubmodules_missing(self):
  130. self.assertEqual(scm.GIT.ListSubmodules('root'), [])
  131. class RealGitTest(fake_repos.FakeReposTestBase):
  132. def setUp(self):
  133. super(RealGitTest, self).setUp()
  134. self.enabled = self.FAKE_REPOS.set_up_git()
  135. if self.enabled:
  136. self.cwd = scm.os.path.join(self.FAKE_REPOS.git_base, 'repo_1')
  137. else:
  138. self.skipTest('git fake repos not available')
  139. def testResolveCommit(self):
  140. with self.assertRaises(Exception):
  141. scm.GIT.ResolveCommit(self.cwd, 'zebra')
  142. with self.assertRaises(Exception):
  143. scm.GIT.ResolveCommit(self.cwd, 'r123456')
  144. first_rev = self.githash('repo_1', 1)
  145. self.assertEqual(first_rev, scm.GIT.ResolveCommit(self.cwd, first_rev))
  146. self.assertEqual(self.githash('repo_1', 2),
  147. scm.GIT.ResolveCommit(self.cwd, 'HEAD'))
  148. def testIsValidRevision(self):
  149. # Sha1's are [0-9a-z]{32}, so starting with a 'z' or 'r' should always
  150. # fail.
  151. self.assertFalse(scm.GIT.IsValidRevision(cwd=self.cwd, rev='zebra'))
  152. self.assertFalse(scm.GIT.IsValidRevision(cwd=self.cwd, rev='r123456'))
  153. # Valid cases
  154. first_rev = self.githash('repo_1', 1)
  155. self.assertTrue(scm.GIT.IsValidRevision(cwd=self.cwd, rev=first_rev))
  156. self.assertTrue(scm.GIT.IsValidRevision(cwd=self.cwd, rev='HEAD'))
  157. def testIsAncestor(self):
  158. self.assertTrue(
  159. scm.GIT.IsAncestor(self.githash('repo_1', 1),
  160. self.githash('repo_1', 2),
  161. cwd=self.cwd))
  162. self.assertFalse(
  163. scm.GIT.IsAncestor(self.githash('repo_1', 2),
  164. self.githash('repo_1', 1),
  165. cwd=self.cwd))
  166. self.assertFalse(scm.GIT.IsAncestor(self.githash('repo_1', 1), 'zebra'))
  167. def testGetAllFiles(self):
  168. self.assertEqual(['DEPS', 'foo bar', 'origin'],
  169. scm.GIT.GetAllFiles(self.cwd))
  170. def testGetSetConfig(self):
  171. key = 'scm.test-key'
  172. self.assertIsNone(scm.GIT.GetConfig(self.cwd, key))
  173. self.assertEqual('default-value',
  174. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  175. scm.GIT.SetConfig(self.cwd, key, 'set-value')
  176. self.assertEqual('set-value', scm.GIT.GetConfig(self.cwd, key))
  177. self.assertEqual('set-value',
  178. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  179. scm.GIT.SetConfig(self.cwd, key, '')
  180. self.assertEqual('', scm.GIT.GetConfig(self.cwd, key))
  181. self.assertEqual('', scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  182. # Clear the cache because we externally manipulate the git config with
  183. # the subprocess call.
  184. scm.GIT.drop_config_cache()
  185. subprocess.run(['git', 'config', key, 'line 1\nline 2\nline 3'],
  186. cwd=self.cwd)
  187. self.assertEqual('line 1\nline 2\nline 3',
  188. scm.GIT.GetConfig(self.cwd, key))
  189. self.assertEqual('line 1\nline 2\nline 3',
  190. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  191. scm.GIT.SetConfig(self.cwd, key)
  192. self.assertIsNone(scm.GIT.GetConfig(self.cwd, key))
  193. self.assertEqual('default-value',
  194. scm.GIT.GetConfig(self.cwd, key, 'default-value'))
  195. # Test missing_ok
  196. key = 'scm.missing-key'
  197. with self.assertRaises(scm.GitConfigUnsetMissingValue):
  198. scm.GIT.SetConfig(self.cwd, key, None, missing_ok=False)
  199. with self.assertRaises(scm.GitConfigUnsetMissingValue):
  200. scm.GIT.SetConfig(self.cwd,
  201. key,
  202. None,
  203. modify_all=True,
  204. missing_ok=False)
  205. with self.assertRaises(scm.GitConfigUnsetMissingValue):
  206. scm.GIT.SetConfig(self.cwd,
  207. key,
  208. None,
  209. value_pattern='some_value',
  210. missing_ok=False)
  211. scm.GIT.SetConfig(self.cwd, key, None)
  212. scm.GIT.SetConfig(self.cwd, key, None, modify_all=True)
  213. scm.GIT.SetConfig(self.cwd, key, None, value_pattern='some_value')
  214. def testGetSetConfigBool(self):
  215. key = 'scm.test-key'
  216. self.assertFalse(scm.GIT.GetConfigBool(self.cwd, key))
  217. scm.GIT.SetConfig(self.cwd, key, 'true')
  218. self.assertTrue(scm.GIT.GetConfigBool(self.cwd, key))
  219. scm.GIT.SetConfig(self.cwd, key)
  220. self.assertFalse(scm.GIT.GetConfigBool(self.cwd, key))
  221. def testGetSetConfigList(self):
  222. key = 'scm.test-key'
  223. self.assertListEqual([], scm.GIT.GetConfigList(self.cwd, key))
  224. scm.GIT.SetConfig(self.cwd, key, 'foo')
  225. scm.GIT.Capture(['config', '--add', key, 'bar'], cwd=self.cwd)
  226. self.assertListEqual(['foo', 'bar'],
  227. scm.GIT.GetConfigList(self.cwd, key))
  228. scm.GIT.SetConfig(self.cwd, key, modify_all=True, value_pattern='^f')
  229. self.assertListEqual(['bar'], scm.GIT.GetConfigList(self.cwd, key))
  230. scm.GIT.SetConfig(self.cwd, key)
  231. self.assertListEqual([], scm.GIT.GetConfigList(self.cwd, key))
  232. def testYieldConfigRegexp(self):
  233. key1 = 'scm.aaa'
  234. key2 = 'scm.aaab'
  235. config = scm.GIT.YieldConfigRegexp(self.cwd, key1)
  236. with self.assertRaises(StopIteration):
  237. next(config)
  238. scm.GIT.SetConfig(self.cwd, key1, 'foo')
  239. scm.GIT.SetConfig(self.cwd, key2, 'bar')
  240. scm.GIT.Capture(['config', '--add', key2, 'baz'], cwd=self.cwd)
  241. config = scm.GIT.YieldConfigRegexp(self.cwd, '^scm\\.aaa')
  242. self.assertEqual((key1, 'foo'), next(config))
  243. self.assertEqual((key2, 'bar'), next(config))
  244. self.assertEqual((key2, 'baz'), next(config))
  245. with self.assertRaises(StopIteration):
  246. next(config)
  247. def testGetSetBranchConfig(self):
  248. branch = scm.GIT.GetBranch(self.cwd)
  249. key = 'scm.test-key'
  250. self.assertIsNone(scm.GIT.GetBranchConfig(self.cwd, branch, key))
  251. self.assertEqual(
  252. 'default-value',
  253. scm.GIT.GetBranchConfig(self.cwd, branch, key, 'default-value'))
  254. scm.GIT.SetBranchConfig(self.cwd, branch, key, 'set-value')
  255. self.assertEqual('set-value',
  256. scm.GIT.GetBranchConfig(self.cwd, branch, key))
  257. self.assertEqual(
  258. 'set-value',
  259. scm.GIT.GetBranchConfig(self.cwd, branch, key, 'default-value'))
  260. self.assertEqual(
  261. 'set-value',
  262. scm.GIT.GetConfig(self.cwd, 'branch.%s.%s' % (branch, key)))
  263. scm.GIT.SetBranchConfig(self.cwd, branch, key)
  264. self.assertIsNone(scm.GIT.GetBranchConfig(self.cwd, branch, key))
  265. def testFetchUpstreamTuple_NoUpstreamFound(self):
  266. self.assertEqual((None, None), scm.GIT.FetchUpstreamTuple(self.cwd))
  267. @mock.patch('scm.GIT.GetRemoteBranches', return_value=['origin/main'])
  268. def testFetchUpstreamTuple_GuessOriginMaster(self, _mockGetRemoteBranches):
  269. self.assertEqual(('origin', 'refs/heads/main'),
  270. scm.GIT.FetchUpstreamTuple(self.cwd))
  271. @mock.patch('scm.GIT.GetRemoteBranches',
  272. return_value=['origin/master', 'origin/main'])
  273. def testFetchUpstreamTuple_GuessOriginMain(self, _mockGetRemoteBranches):
  274. self.assertEqual(('origin', 'refs/heads/main'),
  275. scm.GIT.FetchUpstreamTuple(self.cwd))
  276. def testFetchUpstreamTuple_RietveldUpstreamConfig(self):
  277. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch',
  278. 'rietveld-upstream')
  279. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote',
  280. 'rietveld-remote')
  281. self.assertEqual(('rietveld-remote', 'rietveld-upstream'),
  282. scm.GIT.FetchUpstreamTuple(self.cwd))
  283. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch')
  284. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote')
  285. @mock.patch('scm.GIT.GetBranch', side_effect=callError())
  286. def testFetchUpstreamTuple_NotOnBranch(self, _mockGetBranch):
  287. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch',
  288. 'rietveld-upstream')
  289. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote',
  290. 'rietveld-remote')
  291. self.assertEqual(('rietveld-remote', 'rietveld-upstream'),
  292. scm.GIT.FetchUpstreamTuple(self.cwd))
  293. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-branch')
  294. scm.GIT.SetConfig(self.cwd, 'rietveld.upstream-remote')
  295. def testFetchUpstreamTuple_BranchConfig(self):
  296. branch = scm.GIT.GetBranch(self.cwd)
  297. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge', 'branch-merge')
  298. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote', 'branch-remote')
  299. self.assertEqual(('branch-remote', 'branch-merge'),
  300. scm.GIT.FetchUpstreamTuple(self.cwd))
  301. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge')
  302. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote')
  303. def testFetchUpstreamTuple_AnotherBranchConfig(self):
  304. branch = 'scm-test-branch'
  305. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge', 'other-merge')
  306. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote', 'other-remote')
  307. self.assertEqual(('other-remote', 'other-merge'),
  308. scm.GIT.FetchUpstreamTuple(self.cwd, branch))
  309. scm.GIT.SetBranchConfig(self.cwd, branch, 'merge')
  310. scm.GIT.SetBranchConfig(self.cwd, branch, 'remote')
  311. def testGetBranchRef(self):
  312. self.assertEqual('refs/heads/main', scm.GIT.GetBranchRef(self.cwd))
  313. HEAD = scm.GIT.Capture(['rev-parse', 'HEAD'], cwd=self.cwd)
  314. scm.GIT.Capture(['checkout', HEAD], cwd=self.cwd)
  315. self.assertIsNone(scm.GIT.GetBranchRef(self.cwd))
  316. scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
  317. def testGetBranch(self):
  318. self.assertEqual('main', scm.GIT.GetBranch(self.cwd))
  319. HEAD = scm.GIT.Capture(['rev-parse', 'HEAD'], cwd=self.cwd)
  320. scm.GIT.Capture(['checkout', HEAD], cwd=self.cwd)
  321. self.assertIsNone(scm.GIT.GetBranchRef(self.cwd))
  322. scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
  323. class DiffTestCase(unittest.TestCase):
  324. def setUp(self):
  325. self.root = tempfile.mkdtemp()
  326. os.makedirs(os.path.join(self.root, "foo", "dir"))
  327. with open(os.path.join(self.root, "foo", "file.txt"), "w") as f:
  328. f.write("foo\n")
  329. with open(os.path.join(self.root, "foo", "dir", "file.txt"), "w") as f:
  330. f.write("foo dir\n")
  331. os.makedirs(os.path.join(self.root, "baz_repo"))
  332. with open(os.path.join(self.root, "baz_repo", "file.txt"), "w") as f:
  333. f.write("baz\n")
  334. @mock.patch('scm.GIT.ListSubmodules')
  335. def testGetAllFiles_ReturnsAllFilesIfNoSubmodules(self, mockListSubmodules):
  336. mockListSubmodules.return_value = []
  337. files = scm.DIFF.GetAllFiles(self.root)
  338. if sys.platform.startswith('win'):
  339. self.assertCountEqual(
  340. files,
  341. ["foo\\file.txt", "foo\\dir\\file.txt", "baz_repo\\file.txt"])
  342. else:
  343. self.assertCountEqual(
  344. files,
  345. ["foo/file.txt", "foo/dir/file.txt", "baz_repo/file.txt"])
  346. @mock.patch('scm.GIT.ListSubmodules')
  347. def testGetAllFiles_IgnoresFilesInSubmodules(self, mockListSubmodules):
  348. mockListSubmodules.return_value = ['baz_repo']
  349. files = scm.DIFF.GetAllFiles(self.root)
  350. if sys.platform.startswith('win'):
  351. self.assertCountEqual(
  352. files, ["foo\\file.txt", "foo\\dir\\file.txt", "baz_repo"])
  353. else:
  354. self.assertCountEqual(
  355. files, ["foo/file.txt", "foo/dir/file.txt", "baz_repo"])
  356. class GitConfigStateTestTest(unittest.TestCase):
  357. @staticmethod
  358. def _make(*,
  359. global_state: dict[str, list[str]] | None = None,
  360. system_state: dict[str, list[str]] | None = None):
  361. """_make constructs a GitConfigStateTest with an internal Lock.
  362. If global_state is None, an empty dictionary will be constructed and
  363. returned, otherwise the caller's provided global_state is returned,
  364. unmodified.
  365. Returns (GitConfigStateTest, global_state) - access to global_state must
  366. be manually synchronized with access to GitConfigStateTest, or at least
  367. with GitConfigStateTest.global_state_lock.
  368. """
  369. global_state = global_state or {}
  370. m = scm.GitConfigStateTest(threading.Lock(),
  371. global_state,
  372. system_state=system_state)
  373. return m, global_state
  374. def test_construction_empty(self):
  375. m, gs = self._make()
  376. self.assertDictEqual(gs, {})
  377. self.assertDictEqual(m.load_config(), {})
  378. gs['section.key'] = ['override']
  379. self.assertDictEqual(m.load_config(), {'section.key': ['override']})
  380. def test_construction_global(self):
  381. m, gs = self._make(global_state={'section.key': ['global']})
  382. self.assertDictEqual(gs, {'section.key': ['global']})
  383. self.assertDictEqual(m.load_config(), {'section.key': ['global']})
  384. gs['section.key'] = ['override']
  385. self.assertDictEqual(m.load_config(), {'section.key': ['override']})
  386. def test_construction_system(self):
  387. m, gs = self._make(
  388. global_state={'section.key': ['global']},
  389. system_state={'section.key': ['system']},
  390. )
  391. self.assertDictEqual(gs, {'section.key': ['global']})
  392. self.assertDictEqual(m.load_config(),
  393. {'section.key': ['system', 'global']})
  394. gs['section.key'] = ['override']
  395. self.assertDictEqual(m.load_config(),
  396. {'section.key': ['system', 'override']})
  397. def test_set_config_system(self):
  398. m, _ = self._make()
  399. with self.assertRaises(scm.GitConfigUneditableScope):
  400. m.set_config('section.key',
  401. 'new_global',
  402. append=False,
  403. scope='system')
  404. def test_set_config_unknown(self):
  405. m, _ = self._make()
  406. with self.assertRaises(scm.GitConfigUnknownScope):
  407. m.set_config('section.key',
  408. 'new_global',
  409. append=False,
  410. scope='meepmorp')
  411. def test_set_config_global_append_empty(self):
  412. m, gs = self._make()
  413. self.assertDictEqual(gs, {})
  414. self.assertDictEqual(m.load_config(), {})
  415. m.set_config('section.key', 'new_global', append=True, scope='global')
  416. self.assertDictEqual(m.load_config(), {
  417. 'section.key': ['new_global'],
  418. })
  419. def test_set_config_global(self):
  420. m, gs = self._make()
  421. self.assertDictEqual(gs, {})
  422. self.assertDictEqual(m.load_config(), {})
  423. m.set_config('section.key', 'new_global', append=False, scope='global')
  424. self.assertDictEqual(m.load_config(), {
  425. 'section.key': ['new_global'],
  426. })
  427. m.set_config('section.key', 'new_global2', append=True, scope='global')
  428. self.assertDictEqual(m.load_config(), {
  429. 'section.key': ['new_global', 'new_global2'],
  430. })
  431. self.assertDictEqual(gs, {
  432. 'section.key': ['new_global', 'new_global2'],
  433. })
  434. def test_set_config_multi_global(self):
  435. m, gs = self._make(global_state={
  436. 'section.key': ['1', '2'],
  437. })
  438. m.set_config_multi('section.key',
  439. 'new_global',
  440. value_pattern=None,
  441. scope='global')
  442. self.assertDictEqual(m.load_config(), {
  443. 'section.key': ['new_global'],
  444. })
  445. self.assertDictEqual(gs, {
  446. 'section.key': ['new_global'],
  447. })
  448. m.set_config_multi('othersection.key',
  449. 'newval',
  450. value_pattern=None,
  451. scope='global')
  452. self.assertDictEqual(m.load_config(), {
  453. 'section.key': ['new_global'],
  454. 'othersection.key': ['newval'],
  455. })
  456. self.assertDictEqual(gs, {
  457. 'section.key': ['new_global'],
  458. 'othersection.key': ['newval'],
  459. })
  460. def test_set_config_multi_global_pattern(self):
  461. m, _ = self._make(global_state={
  462. 'section.key': ['1', '1', '2', '2', '2', '3'],
  463. })
  464. m.set_config_multi('section.key',
  465. 'new_global',
  466. value_pattern='2',
  467. scope='global')
  468. self.assertDictEqual(m.load_config(), {
  469. 'section.key': ['1', '1', 'new_global', '3'],
  470. })
  471. m.set_config_multi('section.key',
  472. 'additional',
  473. value_pattern='narp',
  474. scope='global')
  475. self.assertDictEqual(m.load_config(), {
  476. 'section.key': ['1', '1', 'new_global', '3', 'additional'],
  477. })
  478. def test_unset_config_global(self):
  479. m, _ = self._make(global_state={
  480. 'section.key': ['someval'],
  481. })
  482. m.unset_config('section.key', scope='global', missing_ok=False)
  483. self.assertDictEqual(m.load_config(), {})
  484. with self.assertRaises(scm.GitConfigUnsetMissingValue):
  485. m.unset_config('section.key', scope='global', missing_ok=False)
  486. self.assertDictEqual(m.load_config(), {})
  487. m.unset_config('section.key', scope='global', missing_ok=True)
  488. self.assertDictEqual(m.load_config(), {})
  489. def test_unset_config_global_extra(self):
  490. m, _ = self._make(global_state={
  491. 'section.key': ['someval'],
  492. 'extra': ['another'],
  493. })
  494. m.unset_config('section.key', scope='global', missing_ok=False)
  495. self.assertDictEqual(m.load_config(), {
  496. 'extra': ['another'],
  497. })
  498. with self.assertRaises(scm.GitConfigUnsetMissingValue):
  499. m.unset_config('section.key', scope='global', missing_ok=False)
  500. self.assertDictEqual(m.load_config(), {
  501. 'extra': ['another'],
  502. })
  503. m.unset_config('section.key', scope='global', missing_ok=True)
  504. self.assertDictEqual(m.load_config(), {
  505. 'extra': ['another'],
  506. })
  507. def test_unset_config_global_multi(self):
  508. m, _ = self._make(global_state={
  509. 'section.key': ['1', '2'],
  510. })
  511. with self.assertRaises(scm.GitConfigUnsetMultipleValues):
  512. m.unset_config('section.key', scope='global', missing_ok=True)
  513. def test_unset_config_multi_global(self):
  514. m, _ = self._make(global_state={
  515. 'section.key': ['1', '2'],
  516. })
  517. m.unset_config_multi('section.key',
  518. value_pattern=None,
  519. scope='global',
  520. missing_ok=False)
  521. self.assertDictEqual(m.load_config(), {})
  522. with self.assertRaises(scm.GitConfigUnsetMissingValue):
  523. m.unset_config_multi('section.key',
  524. value_pattern=None,
  525. scope='global',
  526. missing_ok=False)
  527. def test_unset_config_multi_global_pattern(self):
  528. m, _ = self._make(global_state={
  529. 'section.key': ['1', '2', '3', '1', '2'],
  530. })
  531. m.unset_config_multi('section.key',
  532. value_pattern='2',
  533. scope='global',
  534. missing_ok=False)
  535. self.assertDictEqual(m.load_config(), {
  536. 'section.key': ['1', '3', '1'],
  537. })
  538. class CanonicalizeGitConfigKeyTest(unittest.TestCase):
  539. def setUp(self) -> None:
  540. self.ck = scm.canonicalize_git_config_key
  541. return super().setUp()
  542. def test_many(self):
  543. self.assertEqual(self.ck("URL.https://SoMeThInG.example.com.INSTEADOF"),
  544. "url.https://SoMeThInG.example.com.insteadof")
  545. def test_three(self):
  546. self.assertEqual(self.ck("A.B.C"), "a.B.c")
  547. self.assertEqual(self.ck("a.B.C"), "a.B.c")
  548. self.assertEqual(self.ck("a.b.C"), "a.b.c")
  549. def test_two(self):
  550. self.assertEqual(self.ck("A.B"), "a.b")
  551. self.assertEqual(self.ck("a.B"), "a.b")
  552. self.assertEqual(self.ck("a.b"), "a.b")
  553. def test_one(self):
  554. with self.assertRaises(scm.GitConfigInvalidKey):
  555. self.ck("KEY")
  556. def test_zero(self):
  557. with self.assertRaises(scm.GitConfigInvalidKey):
  558. self.ck("")
  559. class CachedGitConfigStateTest(unittest.TestCase):
  560. @staticmethod
  561. def _make():
  562. return scm.CachedGitConfigState(
  563. scm.GitConfigStateTest(threading.Lock(), {}))
  564. def test_empty(self):
  565. gcs = self._make()
  566. self.assertListEqual(list(gcs.YieldConfigRegexp()), [])
  567. def test_set_single(self):
  568. gcs = self._make()
  569. gcs.SetConfig('SECTION.VARIABLE', 'value')
  570. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  571. ('section.variable', 'value'),
  572. ])
  573. def test_set_append(self):
  574. gcs = self._make()
  575. gcs.SetConfig('SECTION.VARIABLE', 'value')
  576. gcs.SetConfig('SeCtIoN.vArIaBLe', 'value2', append=True)
  577. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  578. ('section.variable', 'value'),
  579. ('section.variable', 'value2'),
  580. ])
  581. def test_set_global(self):
  582. gcs = self._make()
  583. gcs.SetConfig('SECTION.VARIABLE', 'value')
  584. gcs.SetConfig('SeCtIoN.vArIaBLe', 'value2', append=True)
  585. gcs.SetConfig('SeCtIoN.vArIaBLe', 'gvalue', scope='global')
  586. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  587. ('section.variable', 'gvalue'),
  588. ('section.variable', 'value'),
  589. ('section.variable', 'value2'),
  590. ])
  591. def test_unset_multi_global(self):
  592. gcs = self._make()
  593. gcs.SetConfig('SECTION.VARIABLE', 'value')
  594. gcs.SetConfig('SeCtIoN.vArIaBLe', 'value2', append=True)
  595. gcs.SetConfig('SeCtIoN.vArIaBLe', 'gvalue', scope='global')
  596. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  597. ('section.variable', 'gvalue'),
  598. ('section.variable', 'value'),
  599. ('section.variable', 'value2'),
  600. ])
  601. gcs.SetConfig('section.variable', None, modify_all=True)
  602. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  603. ('section.variable', 'gvalue'),
  604. ])
  605. def test_errors(self):
  606. gcs = self._make()
  607. with self.assertRaises(scm.GitConfigInvalidKey):
  608. gcs.SetConfig('key', 'value')
  609. with self.assertRaises(scm.GitConfigUnknownScope):
  610. gcs.SetConfig('section.variable', 'value', scope='dude')
  611. with self.assertRaises(scm.GitConfigUneditableScope):
  612. gcs.SetConfig('section.variable', 'value', scope='system')
  613. with self.assertRaisesRegex(ValueError,
  614. 'value_pattern.*modify_all.*invalid'):
  615. gcs.SetConfig('section.variable',
  616. 'value',
  617. value_pattern='hi',
  618. modify_all=False)
  619. with self.assertRaisesRegex(ValueError,
  620. 'value_pattern.*append.*invalid'):
  621. gcs.SetConfig('section.variable',
  622. 'value',
  623. value_pattern='hi',
  624. modify_all=True,
  625. append=True)
  626. def test_set_pattern(self):
  627. gcs = self._make()
  628. gcs.SetConfig('section.variable', 'value', append=True)
  629. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  630. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  631. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  632. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  633. gcs.SetConfig('section.variable', 'value', append=True)
  634. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  635. ('section.variable', 'value'),
  636. ('section.variable', 'value_bleem'),
  637. ('section.variable', 'value_bleem'),
  638. ('section.variable', 'value_bleem'),
  639. ('section.variable', 'value_bleem'),
  640. ('section.variable', 'value'),
  641. ])
  642. gcs.SetConfig('section.variable',
  643. 'poof',
  644. value_pattern='.*_bleem',
  645. modify_all=True)
  646. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  647. ('section.variable', 'value'),
  648. ('section.variable', 'poof'),
  649. ('section.variable', 'value'),
  650. ])
  651. def test_set_all(self):
  652. gcs = self._make()
  653. gcs.SetConfig('section.variable', 'value', append=True)
  654. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  655. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  656. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  657. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  658. gcs.SetConfig('section.variable', 'value', append=True)
  659. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  660. ('section.variable', 'value'),
  661. ('section.variable', 'value_bleem'),
  662. ('section.variable', 'value_bleem'),
  663. ('section.variable', 'value_bleem'),
  664. ('section.variable', 'value_bleem'),
  665. ('section.variable', 'value'),
  666. ])
  667. gcs.SetConfig('section.variable', 'poof', modify_all=True)
  668. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  669. ('section.variable', 'poof'),
  670. ])
  671. def test_get_config(self):
  672. gcs = self._make()
  673. gcs.SetConfig('section.variable', 'value', append=True)
  674. gcs.SetConfig('section.variable', 'value_bleem', append=True)
  675. self.assertEqual(gcs.GetConfig('section.varIABLE'), 'value_bleem')
  676. self.assertEqual(gcs.GetConfigBool('section.varIABLE'), False)
  677. self.assertEqual(gcs.GetConfig('section.noexist'), None)
  678. self.assertEqual(gcs.GetConfig('section.noexist', 'dflt'), 'dflt')
  679. gcs.SetConfig('section.variable', 'true', append=True)
  680. self.assertEqual(gcs.GetConfigBool('section.varIABLE'), True)
  681. self.assertListEqual(list(gcs.YieldConfigRegexp()), [
  682. ('section.variable', 'value'),
  683. ('section.variable', 'value_bleem'),
  684. ('section.variable', 'true'),
  685. ])
  686. self.assertListEqual(gcs.GetConfigList('seCTIon.vARIable'), [
  687. 'value',
  688. 'value_bleem',
  689. 'true',
  690. ])
  691. if __name__ == '__main__':
  692. if '-v' in sys.argv:
  693. logging.basicConfig(level=logging.DEBUG)
  694. unittest.main()
  695. # vim: ts=4:sw=4:tw=80:et: