gclient_scm_test.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. #!/usr/bin/env python
  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 gclient_scm.py."""
  6. # pylint: disable=E1103
  7. # Import before super_mox to keep valid references.
  8. from shutil import rmtree
  9. from subprocess import Popen, PIPE, STDOUT
  10. import logging
  11. import os
  12. import re
  13. import sys
  14. import tempfile
  15. import unittest
  16. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  17. from testing_support.super_mox import mox, StdoutCheck, SuperMoxTestBase
  18. from testing_support.super_mox import TestCaseUtils
  19. import gclient_scm
  20. import git_cache
  21. import subprocess2
  22. # Disable global git cache
  23. git_cache.Mirror.SetCachePath(None)
  24. # Shortcut since this function is used often
  25. join = gclient_scm.os.path.join
  26. TIMESTAMP_RE = re.compile('\[[0-9]{1,2}:[0-9]{2}:[0-9]{2}\] (.*)', re.DOTALL)
  27. def strip_timestamps(value):
  28. lines = value.splitlines(True)
  29. for i in xrange(len(lines)):
  30. m = TIMESTAMP_RE.match(lines[i])
  31. if m:
  32. lines[i] = m.group(1)
  33. return ''.join(lines)
  34. class GCBaseTestCase(object):
  35. def assertRaisesError(self, msg, fn, *args, **kwargs):
  36. """Like unittest's assertRaises() but checks for Gclient.Error."""
  37. try:
  38. fn(*args, **kwargs)
  39. except gclient_scm.gclient_utils.Error, e:
  40. self.assertEquals(e.args[0], msg)
  41. else:
  42. self.fail('%s not raised' % msg)
  43. class BaseTestCase(GCBaseTestCase, SuperMoxTestBase):
  44. def setUp(self):
  45. SuperMoxTestBase.setUp(self)
  46. self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'CheckCallAndFilter')
  47. self.mox.StubOutWithMock(gclient_scm.gclient_utils,
  48. 'CheckCallAndFilterAndHeader')
  49. self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'FileRead')
  50. self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'FileWrite')
  51. self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'rmtree')
  52. self.mox.StubOutWithMock(subprocess2, 'communicate')
  53. self.mox.StubOutWithMock(subprocess2, 'Popen')
  54. self._scm_wrapper = gclient_scm.CreateSCM
  55. self._original_GitBinaryExists = gclient_scm.GitWrapper.BinaryExists
  56. gclient_scm.GitWrapper.BinaryExists = staticmethod(lambda : True)
  57. # Absolute path of the fake checkout directory.
  58. self.base_path = join(self.root_dir, self.relpath)
  59. def tearDown(self):
  60. SuperMoxTestBase.tearDown(self)
  61. gclient_scm.GitWrapper.BinaryExists = self._original_GitBinaryExists
  62. class BasicTests(SuperMoxTestBase):
  63. def setUp(self):
  64. SuperMoxTestBase.setUp(self)
  65. def testGetFirstRemoteUrl(self):
  66. REMOTE_STRINGS = [('remote.origin.url E:\\foo\\bar', 'E:\\foo\\bar'),
  67. ('remote.origin.url /b/foo/bar', '/b/foo/bar'),
  68. ('remote.origin.url https://foo/bar', 'https://foo/bar'),
  69. ('remote.origin.url E:\\Fo Bar\\bax', 'E:\\Fo Bar\\bax'),
  70. ('remote.origin.url git://what/"do', 'git://what/"do')]
  71. FAKE_PATH = '/fake/path'
  72. self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'Capture')
  73. for question, _ in REMOTE_STRINGS:
  74. gclient_scm.scm.GIT.Capture(
  75. ['config', '--local', '--get-regexp', r'remote.*.url'],
  76. cwd=FAKE_PATH).AndReturn(question)
  77. self.mox.ReplayAll()
  78. for _, answer in REMOTE_STRINGS:
  79. self.assertEquals(gclient_scm.SCMWrapper._get_first_remote_url(FAKE_PATH),
  80. answer)
  81. def tearDown(self):
  82. SuperMoxTestBase.tearDown(self)
  83. class BaseGitWrapperTestCase(GCBaseTestCase, StdoutCheck, TestCaseUtils,
  84. unittest.TestCase):
  85. """This class doesn't use pymox."""
  86. class OptionsObject(object):
  87. def __init__(self, verbose=False, revision=None):
  88. self.auto_rebase = False
  89. self.verbose = verbose
  90. self.revision = revision
  91. self.deps_os = None
  92. self.force = False
  93. self.reset = False
  94. self.nohooks = False
  95. self.no_history = False
  96. self.upstream = False
  97. self.cache_dir = None
  98. self.merge = False
  99. self.jobs = 1
  100. self.break_repo_locks = False
  101. self.delete_unversioned_trees = False
  102. sample_git_import = """blob
  103. mark :1
  104. data 6
  105. Hello
  106. blob
  107. mark :2
  108. data 4
  109. Bye
  110. reset refs/heads/master
  111. commit refs/heads/master
  112. mark :3
  113. author Bob <bob@example.com> 1253744361 -0700
  114. committer Bob <bob@example.com> 1253744361 -0700
  115. data 8
  116. A and B
  117. M 100644 :1 a
  118. M 100644 :2 b
  119. blob
  120. mark :4
  121. data 10
  122. Hello
  123. You
  124. blob
  125. mark :5
  126. data 8
  127. Bye
  128. You
  129. commit refs/heads/origin
  130. mark :6
  131. author Alice <alice@example.com> 1253744424 -0700
  132. committer Alice <alice@example.com> 1253744424 -0700
  133. data 13
  134. Personalized
  135. from :3
  136. M 100644 :4 a
  137. M 100644 :5 b
  138. blob
  139. mark :7
  140. data 5
  141. Mooh
  142. commit refs/heads/feature
  143. mark :8
  144. author Bob <bob@example.com> 1390311986 -0000
  145. committer Bob <bob@example.com> 1390311986 -0000
  146. data 6
  147. Add C
  148. from :3
  149. M 100644 :7 c
  150. reset refs/heads/master
  151. from :3
  152. """
  153. def Options(self, *args, **kwargs):
  154. return self.OptionsObject(*args, **kwargs)
  155. def checkstdout(self, expected):
  156. value = sys.stdout.getvalue()
  157. sys.stdout.close()
  158. # pylint: disable=no-member
  159. self.assertEquals(expected, strip_timestamps(value))
  160. @staticmethod
  161. def CreateGitRepo(git_import, path):
  162. """Do it for real."""
  163. try:
  164. Popen(['git', 'init', '-q'], stdout=PIPE, stderr=STDOUT,
  165. cwd=path).communicate()
  166. except OSError:
  167. # git is not available, skip this test.
  168. return False
  169. Popen(['git', 'fast-import', '--quiet'], stdin=PIPE, stdout=PIPE,
  170. stderr=STDOUT, cwd=path).communicate(input=git_import)
  171. Popen(['git', 'checkout', '-q'], stdout=PIPE, stderr=STDOUT,
  172. cwd=path).communicate()
  173. Popen(['git', 'remote', 'add', '-f', 'origin', '.'], stdout=PIPE,
  174. stderr=STDOUT, cwd=path).communicate()
  175. Popen(['git', 'checkout', '-b', 'new', 'origin/master', '-q'], stdout=PIPE,
  176. stderr=STDOUT, cwd=path).communicate()
  177. Popen(['git', 'push', 'origin', 'origin/origin:origin/master', '-q'],
  178. stdout=PIPE, stderr=STDOUT, cwd=path).communicate()
  179. Popen(['git', 'config', '--unset', 'remote.origin.fetch'], stdout=PIPE,
  180. stderr=STDOUT, cwd=path).communicate()
  181. Popen(['git', 'config', 'user.email', 'someuser@chromium.org'], stdout=PIPE,
  182. stderr=STDOUT, cwd=path).communicate()
  183. Popen(['git', 'config', 'user.name', 'Some User'], stdout=PIPE,
  184. stderr=STDOUT, cwd=path).communicate()
  185. return True
  186. def _GetAskForDataCallback(self, expected_prompt, return_value):
  187. def AskForData(prompt, options):
  188. self.assertEquals(prompt, expected_prompt)
  189. return return_value
  190. return AskForData
  191. def setUp(self):
  192. TestCaseUtils.setUp(self)
  193. unittest.TestCase.setUp(self)
  194. self.url = 'git://foo'
  195. # The .git suffix allows gclient_scm to recognize the dir as a git repo
  196. # when cloning it locally
  197. self.root_dir = tempfile.mkdtemp('.git')
  198. self.relpath = '.'
  199. self.base_path = join(self.root_dir, self.relpath)
  200. self.enabled = self.CreateGitRepo(self.sample_git_import, self.base_path)
  201. StdoutCheck.setUp(self)
  202. self._original_GitBinaryExists = gclient_scm.GitWrapper.BinaryExists
  203. gclient_scm.GitWrapper.BinaryExists = staticmethod(lambda : True)
  204. def tearDown(self):
  205. try:
  206. rmtree(self.root_dir)
  207. StdoutCheck.tearDown(self)
  208. TestCaseUtils.tearDown(self)
  209. unittest.TestCase.tearDown(self)
  210. finally:
  211. # TODO(maruel): Use auto_stub.TestCase.
  212. gclient_scm.GitWrapper.BinaryExists = self._original_GitBinaryExists
  213. class ManagedGitWrapperTestCase(BaseGitWrapperTestCase):
  214. def testRevertMissing(self):
  215. if not self.enabled:
  216. return
  217. options = self.Options()
  218. file_path = join(self.base_path, 'a')
  219. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  220. relpath=self.relpath)
  221. file_list = []
  222. scm.update(options, None, file_list)
  223. gclient_scm.os.remove(file_path)
  224. file_list = []
  225. scm.revert(options, self.args, file_list)
  226. self.assertEquals(file_list, [file_path])
  227. file_list = []
  228. scm.diff(options, self.args, file_list)
  229. self.assertEquals(file_list, [])
  230. sys.stdout.close()
  231. def testRevertNone(self):
  232. if not self.enabled:
  233. return
  234. options = self.Options()
  235. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  236. relpath=self.relpath)
  237. file_list = []
  238. scm.update(options, None, file_list)
  239. file_list = []
  240. scm.revert(options, self.args, file_list)
  241. self.assertEquals(file_list, [])
  242. self.assertEquals(scm.revinfo(options, self.args, None),
  243. 'a7142dc9f0009350b96a11f372b6ea658592aa95')
  244. sys.stdout.close()
  245. def testRevertModified(self):
  246. if not self.enabled:
  247. return
  248. options = self.Options()
  249. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  250. relpath=self.relpath)
  251. file_list = []
  252. scm.update(options, None, file_list)
  253. file_path = join(self.base_path, 'a')
  254. open(file_path, 'a').writelines('touched\n')
  255. file_list = []
  256. scm.revert(options, self.args, file_list)
  257. self.assertEquals(file_list, [file_path])
  258. file_list = []
  259. scm.diff(options, self.args, file_list)
  260. self.assertEquals(file_list, [])
  261. self.assertEquals(scm.revinfo(options, self.args, None),
  262. 'a7142dc9f0009350b96a11f372b6ea658592aa95')
  263. sys.stdout.close()
  264. def testRevertNew(self):
  265. if not self.enabled:
  266. return
  267. options = self.Options()
  268. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  269. relpath=self.relpath)
  270. file_list = []
  271. scm.update(options, None, file_list)
  272. file_path = join(self.base_path, 'c')
  273. f = open(file_path, 'w')
  274. f.writelines('new\n')
  275. f.close()
  276. Popen(['git', 'add', 'c'], stdout=PIPE,
  277. stderr=STDOUT, cwd=self.base_path).communicate()
  278. file_list = []
  279. scm.revert(options, self.args, file_list)
  280. self.assertEquals(file_list, [file_path])
  281. file_list = []
  282. scm.diff(options, self.args, file_list)
  283. self.assertEquals(file_list, [])
  284. self.assertEquals(scm.revinfo(options, self.args, None),
  285. 'a7142dc9f0009350b96a11f372b6ea658592aa95')
  286. sys.stdout.close()
  287. def testStatusNew(self):
  288. if not self.enabled:
  289. return
  290. options = self.Options()
  291. file_path = join(self.base_path, 'a')
  292. open(file_path, 'a').writelines('touched\n')
  293. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  294. relpath=self.relpath)
  295. file_list = []
  296. scm.status(options, self.args, file_list)
  297. self.assertEquals(file_list, [file_path])
  298. self.checkstdout(
  299. ('\n________ running \'git diff --name-status '
  300. '069c602044c5388d2d15c3f875b057c852003458\' in \'%s\'\nM\ta\n') %
  301. join(self.root_dir, '.'))
  302. def testStatus2New(self):
  303. if not self.enabled:
  304. return
  305. options = self.Options()
  306. expected_file_list = []
  307. for f in ['a', 'b']:
  308. file_path = join(self.base_path, f)
  309. open(file_path, 'a').writelines('touched\n')
  310. expected_file_list.extend([file_path])
  311. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  312. relpath=self.relpath)
  313. file_list = []
  314. scm.status(options, self.args, file_list)
  315. expected_file_list = [join(self.base_path, x) for x in ['a', 'b']]
  316. self.assertEquals(sorted(file_list), expected_file_list)
  317. self.checkstdout(
  318. ('\n________ running \'git diff --name-status '
  319. '069c602044c5388d2d15c3f875b057c852003458\' in \'%s\'\nM\ta\nM\tb\n') %
  320. join(self.root_dir, '.'))
  321. def testUpdateUpdate(self):
  322. if not self.enabled:
  323. return
  324. options = self.Options()
  325. expected_file_list = [join(self.base_path, x) for x in ['a', 'b']]
  326. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  327. relpath=self.relpath)
  328. file_list = []
  329. scm.update(options, (), file_list)
  330. self.assertEquals(file_list, expected_file_list)
  331. self.assertEquals(scm.revinfo(options, (), None),
  332. 'a7142dc9f0009350b96a11f372b6ea658592aa95')
  333. sys.stdout.close()
  334. def testUpdateMerge(self):
  335. if not self.enabled:
  336. return
  337. options = self.Options()
  338. options.merge = True
  339. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  340. relpath=self.relpath)
  341. scm._Run(['checkout', '-q', 'feature'], options)
  342. rev = scm.revinfo(options, (), None)
  343. file_list = []
  344. scm.update(options, (), file_list)
  345. self.assertEquals(file_list, [join(self.base_path, x)
  346. for x in ['a', 'b', 'c']])
  347. # The actual commit that is created is unstable, so we verify its tree and
  348. # parents instead.
  349. self.assertEquals(scm._Capture(['rev-parse', 'HEAD:']),
  350. 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
  351. self.assertEquals(scm._Capture(['rev-parse', 'HEAD^1']), rev)
  352. self.assertEquals(scm._Capture(['rev-parse', 'HEAD^2']),
  353. scm._Capture(['rev-parse', 'origin/master']))
  354. sys.stdout.close()
  355. def testUpdateRebase(self):
  356. if not self.enabled:
  357. return
  358. options = self.Options()
  359. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  360. relpath=self.relpath)
  361. scm._Run(['checkout', '-q', 'feature'], options)
  362. file_list = []
  363. # Fake a 'y' key press.
  364. scm._AskForData = self._GetAskForDataCallback(
  365. 'Cannot fast-forward merge, attempt to rebase? '
  366. '(y)es / (q)uit / (s)kip : ', 'y')
  367. scm.update(options, (), file_list)
  368. self.assertEquals(file_list, [join(self.base_path, x)
  369. for x in ['a', 'b', 'c']])
  370. # The actual commit that is created is unstable, so we verify its tree and
  371. # parent instead.
  372. self.assertEquals(scm._Capture(['rev-parse', 'HEAD:']),
  373. 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
  374. self.assertEquals(scm._Capture(['rev-parse', 'HEAD^']),
  375. scm._Capture(['rev-parse', 'origin/master']))
  376. sys.stdout.close()
  377. def testUpdateReset(self):
  378. if not self.enabled:
  379. return
  380. options = self.Options()
  381. options.reset = True
  382. dir_path = join(self.base_path, 'c')
  383. os.mkdir(dir_path)
  384. open(join(dir_path, 'nested'), 'w').writelines('new\n')
  385. file_path = join(self.base_path, 'file')
  386. open(file_path, 'w').writelines('new\n')
  387. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  388. relpath=self.relpath)
  389. file_list = []
  390. scm.update(options, (), file_list)
  391. self.assert_(gclient_scm.os.path.isdir(dir_path))
  392. self.assert_(gclient_scm.os.path.isfile(file_path))
  393. sys.stdout.close()
  394. def testUpdateResetDeleteUnversionedTrees(self):
  395. if not self.enabled:
  396. return
  397. options = self.Options()
  398. options.reset = True
  399. options.delete_unversioned_trees = True
  400. dir_path = join(self.base_path, 'dir')
  401. os.mkdir(dir_path)
  402. open(join(dir_path, 'nested'), 'w').writelines('new\n')
  403. file_path = join(self.base_path, 'file')
  404. open(file_path, 'w').writelines('new\n')
  405. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  406. relpath=self.relpath)
  407. file_list = []
  408. scm.update(options, (), file_list)
  409. self.assert_(not gclient_scm.os.path.isdir(dir_path))
  410. self.assert_(gclient_scm.os.path.isfile(file_path))
  411. sys.stdout.close()
  412. def testUpdateUnstagedConflict(self):
  413. if not self.enabled:
  414. return
  415. options = self.Options()
  416. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  417. relpath=self.relpath)
  418. file_path = join(self.base_path, 'b')
  419. open(file_path, 'w').writelines('conflict\n')
  420. try:
  421. scm.update(options, (), [])
  422. self.fail()
  423. except (gclient_scm.gclient_utils.Error, subprocess2.CalledProcessError):
  424. # The exact exception text varies across git versions so it's not worth
  425. # verifying it. It's fine as long as it throws.
  426. pass
  427. # Manually flush stdout since we can't verify it's content accurately across
  428. # git versions.
  429. sys.stdout.getvalue()
  430. sys.stdout.close()
  431. @unittest.skip('Skipping until crbug.com/670884 is resolved.')
  432. def testUpdateLocked(self):
  433. if not self.enabled:
  434. return
  435. options = self.Options()
  436. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  437. relpath=self.relpath)
  438. file_path = join(self.base_path, '.git', 'index.lock')
  439. with open(file_path, 'w'):
  440. pass
  441. with self.assertRaisesRegexp(subprocess2.CalledProcessError,
  442. 'Unable to create.*/index.lock'):
  443. scm.update(options, (), [])
  444. sys.stdout.close()
  445. def testUpdateLockedBreak(self):
  446. if not self.enabled:
  447. return
  448. options = self.Options()
  449. options.break_repo_locks = True
  450. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  451. relpath=self.relpath)
  452. file_path = join(self.base_path, '.git', 'index.lock')
  453. with open(file_path, 'w'):
  454. pass
  455. scm.update(options, (), [])
  456. self.assertRegexpMatches(sys.stdout.getvalue(),
  457. "breaking lock.*\.git/index\.lock")
  458. self.assertFalse(os.path.exists(file_path))
  459. sys.stdout.close()
  460. def testUpdateConflict(self):
  461. if not self.enabled:
  462. return
  463. options = self.Options()
  464. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  465. relpath=self.relpath)
  466. file_path = join(self.base_path, 'b')
  467. open(file_path, 'w').writelines('conflict\n')
  468. scm._Run(['commit', '-am', 'test'], options)
  469. scm._AskForData = self._GetAskForDataCallback(
  470. 'Cannot fast-forward merge, attempt to rebase? '
  471. '(y)es / (q)uit / (s)kip : ', 'y')
  472. exception = ('Conflict while rebasing this branch.\n'
  473. 'Fix the conflict and run gclient again.\n'
  474. 'See \'man git-rebase\' for details.\n')
  475. self.assertRaisesError(exception, scm.update, options, (), [])
  476. exception = ('\n____ . at refs/remotes/origin/master\n'
  477. '\tYou have unstaged changes.\n'
  478. '\tPlease commit, stash, or reset.\n')
  479. self.assertRaisesError(exception, scm.update, options, (), [])
  480. sys.stdout.close()
  481. def testRevinfo(self):
  482. if not self.enabled:
  483. return
  484. options = self.Options()
  485. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  486. relpath=self.relpath)
  487. rev_info = scm.revinfo(options, (), None)
  488. self.assertEquals(rev_info, '069c602044c5388d2d15c3f875b057c852003458')
  489. class ManagedGitWrapperTestCaseMox(BaseTestCase):
  490. class OptionsObject(object):
  491. def __init__(self, verbose=False, revision=None, force=False):
  492. self.verbose = verbose
  493. self.revision = revision
  494. self.deps_os = None
  495. self.force = force
  496. self.reset = False
  497. self.nohooks = False
  498. self.break_repo_locks = False
  499. # TODO(maruel): Test --jobs > 1.
  500. self.jobs = 1
  501. def Options(self, *args, **kwargs):
  502. return self.OptionsObject(*args, **kwargs)
  503. def checkstdout(self, expected):
  504. value = sys.stdout.getvalue()
  505. sys.stdout.close()
  506. # pylint: disable=no-member
  507. self.assertEquals(expected, strip_timestamps(value))
  508. def setUp(self):
  509. BaseTestCase.setUp(self)
  510. self.fake_hash_1 = 't0ta11yf4k3'
  511. self.fake_hash_2 = '3v3nf4k3r'
  512. self.url = 'git://foo'
  513. self.root_dir = '/tmp' if sys.platform != 'win32' else 't:\\tmp'
  514. self.relpath = 'fake'
  515. self.base_path = os.path.join(self.root_dir, self.relpath)
  516. self.backup_base_path = os.path.join(self.root_dir,
  517. 'old_%s.git' % self.relpath)
  518. def tearDown(self):
  519. BaseTestCase.tearDown(self)
  520. def testGetUsableRevGit(self):
  521. # pylint: disable=no-member
  522. options = self.Options(verbose=True)
  523. self.mox.StubOutWithMock(gclient_scm.scm.GIT, 'IsValidRevision', True)
  524. gclient_scm.scm.GIT.IsValidRevision(cwd=self.base_path, rev=self.fake_hash_1
  525. ).AndReturn(True)
  526. gclient_scm.scm.GIT.IsValidRevision(cwd=self.base_path, rev='1'
  527. ).AndReturn(False)
  528. gclient_scm.scm.GIT.IsValidRevision(cwd=self.base_path, rev='1'
  529. ).AndReturn(False)
  530. self.mox.StubOutWithMock(gclient_scm.GitWrapper, '_Fetch', True)
  531. # pylint: disable=no-value-for-parameter
  532. gclient_scm.GitWrapper._Fetch(options).AndReturn(None)
  533. gclient_scm.scm.os.path.isdir(self.base_path).AndReturn(True)
  534. gclient_scm.os.path.isdir(self.base_path).AndReturn(True)
  535. self.mox.ReplayAll()
  536. git_scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  537. relpath=self.relpath)
  538. # A [fake] git sha1 with a git repo should work (this is in the case that
  539. # the LKGR gets flipped to git sha1's some day).
  540. self.assertEquals(git_scm.GetUsableRev(self.fake_hash_1, options),
  541. self.fake_hash_1)
  542. # An SVN rev with an existing purely git repo should raise an exception.
  543. self.assertRaises(gclient_scm.gclient_utils.Error,
  544. git_scm.GetUsableRev, '1', options)
  545. def testUpdateNoDotGit(self):
  546. options = self.Options()
  547. gclient_scm.os.path.isdir(
  548. os.path.join(self.base_path, '.git', 'hooks')).AndReturn(False)
  549. gclient_scm.os.path.exists(self.backup_base_path).AndReturn(False)
  550. gclient_scm.os.path.exists(self.base_path).AndReturn(True)
  551. gclient_scm.os.path.isdir(self.base_path).AndReturn(True)
  552. gclient_scm.os.path.exists(os.path.join(self.base_path, '.git')
  553. ).AndReturn(False)
  554. self.mox.StubOutWithMock(gclient_scm.GitWrapper, '_Clone', True)
  555. # pylint: disable=no-value-for-parameter
  556. gclient_scm.GitWrapper._Clone('refs/remotes/origin/master', self.url,
  557. options)
  558. self.mox.StubOutWithMock(gclient_scm.subprocess2, 'check_output', True)
  559. gclient_scm.subprocess2.check_output(
  560. ['git', 'ls-files'], cwd=self.base_path,
  561. env=gclient_scm.scm.GIT.ApplyEnvVars({}), stderr=-1,).AndReturn('')
  562. gclient_scm.subprocess2.check_output(
  563. ['git', 'rev-parse', '--verify', 'HEAD'],
  564. cwd=self.base_path,
  565. env=gclient_scm.scm.GIT.ApplyEnvVars({}),
  566. stderr=-1,
  567. ).AndReturn('')
  568. self.mox.ReplayAll()
  569. scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
  570. relpath=self.relpath)
  571. scm.update(options, None, [])
  572. self.checkstdout('\n')
  573. def testUpdateConflict(self):
  574. options = self.Options()
  575. gclient_scm.os.path.isdir(
  576. os.path.join(self.base_path, '.git', 'hooks')).AndReturn(False)
  577. gclient_scm.os.path.exists(self.backup_base_path).AndReturn(False)
  578. gclient_scm.os.path.exists(self.base_path).AndReturn(True)
  579. gclient_scm.os.path.isdir(self.base_path).AndReturn(True)
  580. gclient_scm.os.path.exists(os.path.join(self.base_path, '.git')
  581. ).AndReturn(False)
  582. self.mox.StubOutWithMock(gclient_scm.GitWrapper, '_Clone', True)
  583. # pylint: disable=no-value-for-parameter
  584. gclient_scm.GitWrapper._Clone(
  585. 'refs/remotes/origin/master', self.url, options
  586. ).AndRaise(gclient_scm.subprocess2.CalledProcessError(None, None, None,
  587. None, None))
  588. self.mox.StubOutWithMock(gclient_scm.GitWrapper, '_DeleteOrMove', True)
  589. gclient_scm.GitWrapper._DeleteOrMove(False)
  590. gclient_scm.GitWrapper._Clone('refs/remotes/origin/master', self.url,
  591. options)
  592. self.mox.StubOutWithMock(gclient_scm.subprocess2, 'check_output', True)
  593. gclient_scm.subprocess2.check_output(
  594. ['git', 'ls-files'], cwd=self.base_path,
  595. env=gclient_scm.scm.GIT.ApplyEnvVars({}), stderr=-1,).AndReturn('')
  596. gclient_scm.subprocess2.check_output(
  597. ['git', 'rev-parse', '--verify', 'HEAD'],
  598. cwd=self.base_path,
  599. env=gclient_scm.scm.GIT.ApplyEnvVars({}),
  600. stderr=-1,
  601. ).AndReturn('')
  602. self.mox.ReplayAll()
  603. scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
  604. relpath=self.relpath)
  605. scm.update(options, None, [])
  606. self.checkstdout('\n')
  607. class UnmanagedGitWrapperTestCase(BaseGitWrapperTestCase):
  608. def checkInStdout(self, expected):
  609. value = sys.stdout.getvalue()
  610. sys.stdout.close()
  611. # pylint: disable=no-member
  612. self.assertIn(expected, value)
  613. def checkNotInStdout(self, expected):
  614. value = sys.stdout.getvalue()
  615. sys.stdout.close()
  616. # pylint: disable=no-member
  617. self.assertNotIn(expected, value)
  618. def getCurrentBranch(self):
  619. # Returns name of current branch or HEAD for detached HEAD
  620. branch = gclient_scm.scm.GIT.Capture(['rev-parse', '--abbrev-ref', 'HEAD'],
  621. cwd=self.base_path)
  622. if branch == 'HEAD':
  623. return None
  624. return branch
  625. def testUpdateClone(self):
  626. if not self.enabled:
  627. return
  628. options = self.Options()
  629. origin_root_dir = self.root_dir
  630. self.root_dir = tempfile.mkdtemp()
  631. self.relpath = '.'
  632. self.base_path = join(self.root_dir, self.relpath)
  633. scm = gclient_scm.CreateSCM(url=origin_root_dir,
  634. root_dir=self.root_dir,
  635. relpath=self.relpath)
  636. expected_file_list = [join(self.base_path, "a"),
  637. join(self.base_path, "b")]
  638. file_list = []
  639. options.revision = 'unmanaged'
  640. scm.update(options, (), file_list)
  641. self.assertEquals(file_list, expected_file_list)
  642. self.assertEquals(scm.revinfo(options, (), None),
  643. '069c602044c5388d2d15c3f875b057c852003458')
  644. # indicates detached HEAD
  645. self.assertEquals(self.getCurrentBranch(), None)
  646. self.checkInStdout(
  647. 'Checked out refs/remotes/origin/master to a detached HEAD')
  648. rmtree(origin_root_dir)
  649. def testUpdateCloneOnCommit(self):
  650. if not self.enabled:
  651. return
  652. options = self.Options()
  653. origin_root_dir = self.root_dir
  654. self.root_dir = tempfile.mkdtemp()
  655. self.relpath = '.'
  656. self.base_path = join(self.root_dir, self.relpath)
  657. url_with_commit_ref = origin_root_dir +\
  658. '@a7142dc9f0009350b96a11f372b6ea658592aa95'
  659. scm = gclient_scm.CreateSCM(url=url_with_commit_ref,
  660. root_dir=self.root_dir,
  661. relpath=self.relpath)
  662. expected_file_list = [join(self.base_path, "a"),
  663. join(self.base_path, "b")]
  664. file_list = []
  665. options.revision = 'unmanaged'
  666. scm.update(options, (), file_list)
  667. self.assertEquals(file_list, expected_file_list)
  668. self.assertEquals(scm.revinfo(options, (), None),
  669. 'a7142dc9f0009350b96a11f372b6ea658592aa95')
  670. # indicates detached HEAD
  671. self.assertEquals(self.getCurrentBranch(), None)
  672. self.checkInStdout(
  673. 'Checked out a7142dc9f0009350b96a11f372b6ea658592aa95 to a detached HEAD')
  674. rmtree(origin_root_dir)
  675. def testUpdateCloneOnBranch(self):
  676. if not self.enabled:
  677. return
  678. options = self.Options()
  679. origin_root_dir = self.root_dir
  680. self.root_dir = tempfile.mkdtemp()
  681. self.relpath = '.'
  682. self.base_path = join(self.root_dir, self.relpath)
  683. url_with_branch_ref = origin_root_dir + '@feature'
  684. scm = gclient_scm.CreateSCM(url=url_with_branch_ref,
  685. root_dir=self.root_dir,
  686. relpath=self.relpath)
  687. expected_file_list = [join(self.base_path, "a"),
  688. join(self.base_path, "b"),
  689. join(self.base_path, "c")]
  690. file_list = []
  691. options.revision = 'unmanaged'
  692. scm.update(options, (), file_list)
  693. self.assertEquals(file_list, expected_file_list)
  694. self.assertEquals(scm.revinfo(options, (), None),
  695. '9a51244740b25fa2ded5252ca00a3178d3f665a9')
  696. self.assertEquals(self.getCurrentBranch(), 'feature')
  697. self.checkNotInStdout('Checked out feature to a detached HEAD')
  698. rmtree(origin_root_dir)
  699. def testUpdateCloneOnFetchedRemoteBranch(self):
  700. if not self.enabled:
  701. return
  702. options = self.Options()
  703. origin_root_dir = self.root_dir
  704. self.root_dir = tempfile.mkdtemp()
  705. self.relpath = '.'
  706. self.base_path = join(self.root_dir, self.relpath)
  707. url_with_branch_ref = origin_root_dir + '@refs/remotes/origin/feature'
  708. scm = gclient_scm.CreateSCM(url=url_with_branch_ref,
  709. root_dir=self.root_dir,
  710. relpath=self.relpath)
  711. expected_file_list = [join(self.base_path, "a"),
  712. join(self.base_path, "b"),
  713. join(self.base_path, "c")]
  714. file_list = []
  715. options.revision = 'unmanaged'
  716. scm.update(options, (), file_list)
  717. self.assertEquals(file_list, expected_file_list)
  718. self.assertEquals(scm.revinfo(options, (), None),
  719. '9a51244740b25fa2ded5252ca00a3178d3f665a9')
  720. # indicates detached HEAD
  721. self.assertEquals(self.getCurrentBranch(), None)
  722. self.checkInStdout(
  723. 'Checked out refs/remotes/origin/feature to a detached HEAD')
  724. rmtree(origin_root_dir)
  725. def testUpdateCloneOnTrueRemoteBranch(self):
  726. if not self.enabled:
  727. return
  728. options = self.Options()
  729. origin_root_dir = self.root_dir
  730. self.root_dir = tempfile.mkdtemp()
  731. self.relpath = '.'
  732. self.base_path = join(self.root_dir, self.relpath)
  733. url_with_branch_ref = origin_root_dir + '@refs/heads/feature'
  734. scm = gclient_scm.CreateSCM(url=url_with_branch_ref,
  735. root_dir=self.root_dir,
  736. relpath=self.relpath)
  737. expected_file_list = [join(self.base_path, "a"),
  738. join(self.base_path, "b"),
  739. join(self.base_path, "c")]
  740. file_list = []
  741. options.revision = 'unmanaged'
  742. scm.update(options, (), file_list)
  743. self.assertEquals(file_list, expected_file_list)
  744. self.assertEquals(scm.revinfo(options, (), None),
  745. '9a51244740b25fa2ded5252ca00a3178d3f665a9')
  746. # @refs/heads/feature is AKA @refs/remotes/origin/feature in the clone, so
  747. # should be treated as such by gclient.
  748. # TODO(mmoss): Though really, we should only allow DEPS to specify branches
  749. # as they are known in the upstream repo, since the mapping into the local
  750. # repo can be modified by users (or we might even want to change the gclient
  751. # defaults at some point). But that will take more work to stop using
  752. # refs/remotes/ everywhere that we do (and to stop assuming a DEPS ref will
  753. # always resolve locally, like when passing them to show-ref or rev-list).
  754. self.assertEquals(self.getCurrentBranch(), None)
  755. self.checkInStdout(
  756. 'Checked out refs/remotes/origin/feature to a detached HEAD')
  757. rmtree(origin_root_dir)
  758. def testUpdateUpdate(self):
  759. if not self.enabled:
  760. return
  761. options = self.Options()
  762. expected_file_list = []
  763. scm = gclient_scm.CreateSCM(url=self.url, root_dir=self.root_dir,
  764. relpath=self.relpath)
  765. file_list = []
  766. options.revision = 'unmanaged'
  767. scm.update(options, (), file_list)
  768. self.assertEquals(file_list, expected_file_list)
  769. self.assertEquals(scm.revinfo(options, (), None),
  770. '069c602044c5388d2d15c3f875b057c852003458')
  771. self.checkstdout('________ unmanaged solution; skipping .\n')
  772. if __name__ == '__main__':
  773. level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
  774. logging.basicConfig(
  775. level=level,
  776. format='%(asctime).19s %(levelname)s %(filename)s:'
  777. '%(lineno)s %(message)s')
  778. unittest.main()
  779. # vim: ts=2:sw=2:tw=80:et: