scm_unittest.py 36 KB

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