gclient_smoketest.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  1. #!/usr/bin/env python
  2. # Copyright (c) 2011 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. """Smoke tests for gclient.py.
  6. Shell out 'gclient' and run basic conformance tests.
  7. This test assumes GClientSmokeBase.URL_BASE is valid.
  8. """
  9. # pylint: disable=E1103,W0403
  10. import logging
  11. import os
  12. import re
  13. import subprocess
  14. import sys
  15. import unittest
  16. ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
  17. sys.path.insert(0, os.path.dirname(ROOT_DIR))
  18. from tests.fake_repos import join, write, FakeReposTestBase
  19. import subprocess2
  20. GCLIENT_PATH = os.path.join(os.path.dirname(ROOT_DIR), 'gclient')
  21. COVERAGE = False
  22. class GClientSmokeBase(FakeReposTestBase):
  23. def setUp(self):
  24. super(GClientSmokeBase, self).setUp()
  25. # Make sure it doesn't try to auto update when testing!
  26. self.env = os.environ.copy()
  27. self.env['DEPOT_TOOLS_UPDATE'] = '0'
  28. def gclient(self, cmd, cwd=None):
  29. if not cwd:
  30. cwd = self.root_dir
  31. if COVERAGE:
  32. # Don't use the wrapper script.
  33. cmd_base = ['coverage', 'run', '-a', GCLIENT_PATH + '.py']
  34. else:
  35. cmd_base = [GCLIENT_PATH]
  36. cmd = cmd_base + cmd
  37. process = subprocess.Popen(cmd, cwd=cwd, env=self.env,
  38. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  39. shell=sys.platform.startswith('win'))
  40. (stdout, stderr) = process.communicate()
  41. logging.debug("XXX: %s\n%s\nXXX" % (' '.join(cmd), stdout))
  42. logging.debug("YYY: %s\n%s\nYYY" % (' '.join(cmd), stderr))
  43. return (stdout.replace('\r\n', '\n'), stderr.replace('\r\n', '\n'),
  44. process.returncode)
  45. def parseGclient(self, cmd, items, expected_stderr='', untangle=False):
  46. """Parse gclient's output to make it easier to test.
  47. If untangle is True, tries to sort out the output from parallel checkout."""
  48. (stdout, stderr, returncode) = self.gclient(cmd)
  49. if untangle:
  50. tasks = {}
  51. remaining = []
  52. for line in stdout.splitlines(False):
  53. m = re.match(r'^(\d)+>(.*)$', line)
  54. if not m:
  55. remaining.append(line)
  56. else:
  57. self.assertEquals([], remaining)
  58. tasks.setdefault(int(m.group(1)), []).append(m.group(2))
  59. out = []
  60. for key in sorted(tasks.iterkeys()):
  61. out.extend(tasks[key])
  62. out.extend(remaining)
  63. stdout = '\n'.join(out)
  64. self.checkString(expected_stderr, stderr)
  65. self.assertEquals(0, returncode)
  66. return self.checkBlock(stdout, items)
  67. def splitBlock(self, stdout):
  68. """Split gclient's output into logical execution blocks.
  69. ___ running 'foo' at '/bar'
  70. (...)
  71. ___ running 'baz' at '/bar'
  72. (...)
  73. will result in 2 items of len((...).splitlines()) each.
  74. """
  75. results = []
  76. for line in stdout.splitlines(False):
  77. # Intentionally skips empty lines.
  78. if not line:
  79. continue
  80. if line.startswith('__'):
  81. match = re.match(r'^________ ([a-z]+) \'(.*)\' in \'(.*)\'$', line)
  82. if not match:
  83. match = re.match(r'^_____ (.*) is missing, synching instead$', line)
  84. if match:
  85. # Blah, it's when a dependency is deleted, we should probably not
  86. # output this message.
  87. results.append([line])
  88. elif (
  89. not re.match(
  90. r'_____ [^ ]+ : Attempting rebase onto [0-9a-f]+...',
  91. line) and
  92. not re.match(r'_____ [^ ]+ at [^ ]+', line)):
  93. # The two regexp above are a bit too broad, they are necessary only
  94. # for git checkouts.
  95. self.fail(line)
  96. else:
  97. results.append([[match.group(1), match.group(2), match.group(3)]])
  98. else:
  99. if not results:
  100. # TODO(maruel): gclient's git stdout is inconsistent.
  101. # This should fail the test instead!!
  102. pass
  103. else:
  104. results[-1].append(line)
  105. return results
  106. def checkBlock(self, stdout, items):
  107. results = self.splitBlock(stdout)
  108. for i in xrange(min(len(results), len(items))):
  109. if isinstance(items[i], (list, tuple)):
  110. verb = items[i][0]
  111. path = items[i][1]
  112. else:
  113. verb = items[i]
  114. path = self.root_dir
  115. self.checkString(results[i][0][0], verb, (i, results[i][0][0], verb))
  116. if sys.platform == 'win32':
  117. # Make path lower case since casing can change randomly.
  118. self.checkString(
  119. results[i][0][2].lower(),
  120. path.lower(),
  121. (i, results[i][0][2].lower(), path.lower()))
  122. else:
  123. self.checkString(results[i][0][2], path, (i, results[i][0][2], path))
  124. self.assertEquals(len(results), len(items), (stdout, items, len(results)))
  125. return results
  126. @staticmethod
  127. def svnBlockCleanup(out):
  128. """Work around svn status difference between svn 1.5 and svn 1.6
  129. I don't know why but on Windows they are reversed. So sorts the items."""
  130. for i in xrange(len(out)):
  131. if len(out[i]) < 2:
  132. continue
  133. out[i] = [out[i][0]] + sorted([x[1:].strip() for x in out[i][1:]])
  134. return out
  135. class GClientSmoke(GClientSmokeBase):
  136. """Doesn't require either svnserve nor git-daemon."""
  137. @property
  138. def svn_base(self):
  139. return 'svn://random.server/svn/'
  140. @property
  141. def git_base(self):
  142. return 'git://random.server/git/'
  143. def testHelp(self):
  144. """testHelp: make sure no new command was added."""
  145. result = self.gclient(['help'])
  146. # Roughly, not too short, not too long.
  147. self.assertTrue(1000 < len(result[0]) and len(result[0]) < 2000)
  148. self.assertEquals(0, len(result[1]))
  149. self.assertEquals(0, result[2])
  150. def testUnknown(self):
  151. result = self.gclient(['foo'])
  152. # Roughly, not too short, not too long.
  153. self.assertTrue(1000 < len(result[0]) and len(result[0]) < 2000)
  154. self.assertEquals(0, len(result[1]))
  155. self.assertEquals(0, result[2])
  156. def testNotConfigured(self):
  157. res = ('', 'Error: client not configured; see \'gclient config\'\n', 1)
  158. self.check(res, self.gclient(['cleanup']))
  159. self.check(res, self.gclient(['diff']))
  160. self.check(res, self.gclient(['pack']))
  161. self.check(res, self.gclient(['revert']))
  162. self.check(res, self.gclient(['revinfo']))
  163. self.check(res, self.gclient(['runhooks']))
  164. self.check(res, self.gclient(['status']))
  165. self.check(res, self.gclient(['sync']))
  166. self.check(res, self.gclient(['update']))
  167. def testConfig(self):
  168. p = join(self.root_dir, '.gclient')
  169. def test(cmd, expected):
  170. if os.path.exists(p):
  171. os.remove(p)
  172. results = self.gclient(cmd)
  173. self.check(('', '', 0), results)
  174. self.checkString(expected, open(p, 'rU').read())
  175. test(['config', self.svn_base + 'trunk/src/'],
  176. ('solutions = [\n'
  177. ' { "name" : "src",\n'
  178. ' "url" : "%strunk/src",\n'
  179. ' "custom_deps" : {\n'
  180. ' },\n'
  181. ' "safesync_url": "",\n'
  182. ' },\n'
  183. ']\n') % self.svn_base)
  184. test(['config', self.git_base + 'repo_1', '--name', 'src'],
  185. ('solutions = [\n'
  186. ' { "name" : "src",\n'
  187. ' "url" : "%srepo_1",\n'
  188. ' "custom_deps" : {\n'
  189. ' },\n'
  190. ' "safesync_url": "",\n'
  191. ' },\n'
  192. ']\n') % self.git_base)
  193. test(['config', 'foo', 'faa'],
  194. 'solutions = [\n'
  195. ' { "name" : "foo",\n'
  196. ' "url" : "foo",\n'
  197. ' "custom_deps" : {\n'
  198. ' },\n'
  199. ' "safesync_url": "faa",\n'
  200. ' },\n'
  201. ']\n')
  202. test(['config', '--spec', '["blah blah"]'], '["blah blah"]')
  203. os.remove(p)
  204. results = self.gclient(['config', 'foo', 'faa', 'fuu'])
  205. err = ('Usage: gclient.py config [options] [url] [safesync url]\n\n'
  206. 'gclient.py: error: Inconsistent arguments. Use either --spec or one'
  207. ' or 2 args\n')
  208. self.check(('', err, 2), results)
  209. self.assertFalse(os.path.exists(join(self.root_dir, '.gclient')))
  210. def testSolutionNone(self):
  211. results = self.gclient(['config', '--spec',
  212. 'solutions=[{"name": "./", "url": None}]'])
  213. self.check(('', '', 0), results)
  214. results = self.gclient(['sync'])
  215. self.check(('', '', 0), results)
  216. self.assertTree({})
  217. results = self.gclient(['revinfo'])
  218. self.check(('./: None\n', '', 0), results)
  219. self.check(('', '', 0), self.gclient(['cleanup']))
  220. self.check(('', '', 0), self.gclient(['diff']))
  221. self.assertTree({})
  222. self.check(('', '', 0), self.gclient(['pack']))
  223. self.check(('', '', 0), self.gclient(['revert']))
  224. self.assertTree({})
  225. self.check(('', '', 0), self.gclient(['runhooks']))
  226. self.assertTree({})
  227. self.check(('', '', 0), self.gclient(['status']))
  228. def testDifferentTopLevelDirectory(self):
  229. # Check that even if the .gclient file does not mention the directory src
  230. # itself, but it is included via dependencies, the .gclient file is used.
  231. self.gclient(['config', self.svn_base + 'trunk/src.DEPS'])
  232. deps = join(self.root_dir, 'src.DEPS')
  233. os.mkdir(deps)
  234. write(join(deps, 'DEPS'),
  235. 'deps = { "src": "%strunk/src" }' % (self.svn_base))
  236. src = join(self.root_dir, 'src')
  237. os.mkdir(src)
  238. res = self.gclient(['status', '--jobs', '1'], src)
  239. self.checkBlock(res[0], [('running', deps), ('running', src)])
  240. class GClientSmokeSVN(GClientSmokeBase):
  241. def setUp(self):
  242. super(GClientSmokeSVN, self).setUp()
  243. self.enabled = self.FAKE_REPOS.set_up_svn()
  244. def testSync(self):
  245. # TODO(maruel): safesync.
  246. if not self.enabled:
  247. return
  248. self.gclient(['config', self.svn_base + 'trunk/src/'])
  249. # Test unversioned checkout.
  250. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1'],
  251. ['running', 'running',
  252. # This is due to the way svn update is called for a
  253. # single file when File() is used in a DEPS file.
  254. ('running', os.path.join(self.root_dir, 'src', 'file', 'other')),
  255. 'running', 'running', 'running', 'running'])
  256. tree = self.mangle_svn_tree(
  257. ('trunk/src@2', 'src'),
  258. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  259. ('trunk/other@2', 'src/other'))
  260. tree['src/file/other/DEPS'] = (
  261. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  262. tree['src/svn_hooked1'] = 'svn_hooked1'
  263. self.assertTree(tree)
  264. # Manually remove svn_hooked1 before synching to make sure it's not
  265. # recreated.
  266. os.remove(join(self.root_dir, 'src', 'svn_hooked1'))
  267. # Test incremental versioned sync: sync backward.
  268. self.parseGclient(
  269. ['sync', '--revision', 'src@1', '--deps', 'mac',
  270. '--delete_unversioned_trees', '--jobs', '1'],
  271. ['running', 'running', 'running', 'running', 'deleting'])
  272. tree = self.mangle_svn_tree(
  273. ('trunk/src@1', 'src'),
  274. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  275. ('trunk/other@1', 'src/other'),
  276. ('trunk/third_party/foo@2', 'src/third_party/prout'))
  277. tree['src/file/other/DEPS'] = (
  278. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  279. self.assertTree(tree)
  280. # Test incremental sync: delete-unversioned_trees isn't there.
  281. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1'],
  282. ['running', 'running', 'running', 'running', 'running'])
  283. tree = self.mangle_svn_tree(
  284. ('trunk/src@2', 'src'),
  285. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  286. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  287. ('trunk/other@2', 'src/other'),
  288. ('trunk/third_party/foo@2', 'src/third_party/prout'))
  289. tree['src/file/other/DEPS'] = (
  290. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  291. tree['src/svn_hooked1'] = 'svn_hooked1'
  292. self.assertTree(tree)
  293. def testSyncTransitive(self):
  294. # TODO(maruel): safesync.
  295. if not self.enabled:
  296. return
  297. self.gclient(['config', self.svn_base + 'trunk/src/'])
  298. # Make sure we can populate a new repository with --transitive.
  299. self.parseGclient(
  300. ['sync', '--transitive', '--revision', 'src@1', '--deps', 'mac',
  301. '--jobs', '1'],
  302. ['running', 'running', 'running', 'running'])
  303. tree = self.mangle_svn_tree(
  304. ('trunk/src@1', 'src'),
  305. ('trunk/third_party/foo@1', 'src/third_party/fpp'),
  306. ('trunk/other@1', 'src/other'),
  307. ('trunk/third_party/foo@1', 'src/third_party/prout'))
  308. # Get up to date, so we can test synching back.
  309. self.gclient(['sync', '--deps', 'mac', '--jobs', '1'])
  310. # Manually remove svn_hooked1 before synching to make sure it's not
  311. # recreated.
  312. os.remove(join(self.root_dir, 'src', 'svn_hooked1'))
  313. self.parseGclient(
  314. ['sync', '--transitive', '--revision', 'src@1', '--deps', 'mac',
  315. '--delete_unversioned_trees', '--jobs', '1'],
  316. ['running', 'running', 'running', 'running', 'deleting'])
  317. tree = self.mangle_svn_tree(
  318. ('trunk/src@1', 'src'),
  319. ('trunk/third_party/foo@1', 'src/third_party/fpp'),
  320. ('trunk/other@1', 'src/other'),
  321. ('trunk/third_party/foo@1', 'src/third_party/prout'))
  322. tree['src/file/other/DEPS'] = (
  323. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  324. self.assertTree(tree)
  325. def testSyncIgnoredSolutionName(self):
  326. """TODO(maruel): This will become an error soon."""
  327. if not self.enabled:
  328. return
  329. self.gclient(['config', self.svn_base + 'trunk/src/'])
  330. results = self.gclient(
  331. ['sync', '--deps', 'mac', '-r', 'invalid@1', '--jobs', '1'])
  332. self.checkBlock(results[0], [
  333. 'running', 'running',
  334. # This is due to the way svn update is called for a single file when
  335. # File() is used in a DEPS file.
  336. ('running', os.path.join(self.root_dir, 'src', 'file', 'other')),
  337. 'running', 'running', 'running', 'running'])
  338. self.checkString('Please fix your script, having invalid --revision flags '
  339. 'will soon considered an error.\n', results[1])
  340. self.assertEquals(0, results[2])
  341. tree = self.mangle_svn_tree(
  342. ('trunk/src@2', 'src'),
  343. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  344. ('trunk/other@2', 'src/other'))
  345. tree['src/file/other/DEPS'] = (
  346. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  347. tree['src/svn_hooked1'] = 'svn_hooked1'
  348. self.assertTree(tree)
  349. def testSyncNoSolutionName(self):
  350. # When no solution name is provided, gclient uses the first solution listed.
  351. if not self.enabled:
  352. return
  353. self.gclient(['config', self.svn_base + 'trunk/src/'])
  354. self.parseGclient(['sync', '--deps', 'mac', '-r', '1', '--jobs', '1'],
  355. ['running', 'running', 'running', 'running'])
  356. tree = self.mangle_svn_tree(
  357. ('trunk/src@1', 'src'),
  358. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  359. ('trunk/other@1', 'src/other'),
  360. ('trunk/third_party/foo@2', 'src/third_party/prout'))
  361. self.assertTree(tree)
  362. def testSyncJobs(self):
  363. if not self.enabled:
  364. return
  365. # TODO(maruel): safesync.
  366. self.gclient(['config', self.svn_base + 'trunk/src/'])
  367. # Test unversioned checkout.
  368. self.parseGclient(
  369. ['sync', '--deps', 'mac', '--jobs', '8'],
  370. ['running', 'running',
  371. # This is due to the way svn update is called for a
  372. # single file when File() is used in a DEPS file.
  373. ('running', os.path.join(self.root_dir, 'src', 'file', 'other')),
  374. 'running', 'running', 'running', 'running'],
  375. untangle=True)
  376. tree = self.mangle_svn_tree(
  377. ('trunk/src@2', 'src'),
  378. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  379. ('trunk/other@2', 'src/other'))
  380. tree['src/file/other/DEPS'] = (
  381. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  382. tree['src/svn_hooked1'] = 'svn_hooked1'
  383. self.assertTree(tree)
  384. # Manually remove svn_hooked1 before synching to make sure it's not
  385. # recreated.
  386. os.remove(join(self.root_dir, 'src', 'svn_hooked1'))
  387. # Test incremental versioned sync: sync backward.
  388. self.parseGclient(
  389. ['sync', '--revision', 'src@1', '--deps', 'mac',
  390. '--delete_unversioned_trees', '--jobs', '8'],
  391. ['running', 'running', 'running', 'running', 'deleting'],
  392. untangle=True)
  393. tree = self.mangle_svn_tree(
  394. ('trunk/src@1', 'src'),
  395. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  396. ('trunk/other@1', 'src/other'),
  397. ('trunk/third_party/foo@2', 'src/third_party/prout'))
  398. tree['src/file/other/DEPS'] = (
  399. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  400. self.assertTree(tree)
  401. # Test incremental sync: delete-unversioned_trees isn't there.
  402. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '8'],
  403. ['running', 'running', 'running', 'running', 'running'],
  404. untangle=True)
  405. tree = self.mangle_svn_tree(
  406. ('trunk/src@2', 'src'),
  407. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  408. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  409. ('trunk/other@2', 'src/other'),
  410. ('trunk/third_party/foo@2', 'src/third_party/prout'))
  411. tree['src/file/other/DEPS'] = (
  412. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  413. tree['src/svn_hooked1'] = 'svn_hooked1'
  414. self.assertTree(tree)
  415. def testSyncCustomDeps(self):
  416. if not self.enabled:
  417. return
  418. out = (
  419. 'solutions = [\n'
  420. ' { "name" : "src",\n'
  421. ' "url" : "%(base)s/src",\n'
  422. ' "custom_deps" : {\n'
  423. # Remove 2 deps, change 1, add 1.
  424. ' "src/other": None,\n'
  425. ' "src/third_party/foo": \'%(base)s/third_party/prout\',\n'
  426. ' "src/file/other": None,\n'
  427. ' "new_deps": "/trunk/src/third_party",\n'
  428. ' },\n'
  429. ' "safesync_url": "",\n'
  430. ' },\n'
  431. ']\n\n' %
  432. { 'base': self.svn_base + 'trunk' })
  433. fileobj = open(os.path.join(self.root_dir, '.gclient'), 'w')
  434. fileobj.write(out)
  435. fileobj.close()
  436. self.parseGclient(
  437. ['sync', '--deps', 'mac', '--jobs', '1'],
  438. ['running', 'running', 'running', 'running'],
  439. untangle=True)
  440. tree = self.mangle_svn_tree(
  441. ('trunk/src@2', 'src'),
  442. ('trunk/third_party/prout@2', 'src/third_party/foo'),
  443. ('trunk/src/third_party@2', 'new_deps'))
  444. tree['src/svn_hooked1'] = 'svn_hooked1'
  445. self.assertTree(tree)
  446. def testSyncCustomDepsNoDeps(self):
  447. if not self.enabled:
  448. return
  449. out = (
  450. 'solutions = [\n'
  451. # This directory has no DEPS file.
  452. ' { "name" : "src/third_party",\n'
  453. ' "url" : "%(base)s/src/third_party",\n'
  454. ' "custom_deps" : {\n'
  455. # Add 1.
  456. ' "src/other": \'/trunk/other\',\n'
  457. ' },\n'
  458. ' "safesync_url": "",\n'
  459. ' },\n'
  460. ']\n\n' %
  461. { 'base': self.svn_base + 'trunk' })
  462. fileobj = open(os.path.join(self.root_dir, '.gclient'), 'w')
  463. fileobj.write(out)
  464. fileobj.close()
  465. self.parseGclient(
  466. ['sync', '--deps', 'mac', '--jobs', '1'],
  467. ['running', 'running'],
  468. untangle=True)
  469. tree = self.mangle_svn_tree(
  470. ('trunk/src/third_party@2', 'src/third_party'),
  471. ('trunk/other@2', 'src/other'))
  472. self.assertTree(tree)
  473. def testRevertAndStatus(self):
  474. if not self.enabled:
  475. return
  476. self.gclient(['config', self.svn_base + 'trunk/src/'])
  477. # Tested in testSync.
  478. self.gclient(['sync', '--deps', 'mac'])
  479. write(join(self.root_dir, 'src', 'other', 'hi'), 'Hey!')
  480. out = self.parseGclient(['status', '--deps', 'mac', '--jobs', '1'],
  481. [['running', join(self.root_dir, 'src')],
  482. ['running', join(self.root_dir, 'src', 'other')]])
  483. out = self.svnBlockCleanup(out)
  484. self.checkString('file', out[0][1])
  485. self.checkString('other', out[0][2])
  486. self.checkString('svn_hooked1', out[0][3])
  487. self.checkString(join('third_party', 'foo'), out[0][4])
  488. self.checkString('hi', out[1][1])
  489. self.assertEquals(5, len(out[0]))
  490. self.assertEquals(2, len(out[1]))
  491. # Revert implies --force implies running hooks without looking at pattern
  492. # matching.
  493. results = self.gclient(['revert', '--deps', 'mac', '--jobs', '1'])
  494. out = self.splitBlock(results[0])
  495. # src, src/other is missing, src/other, src/third_party/foo is missing,
  496. # src/third_party/foo, 2 svn hooks, 3 related to File().
  497. self.assertEquals(10, len(out))
  498. self.checkString('', results[1])
  499. self.assertEquals(0, results[2])
  500. tree = self.mangle_svn_tree(
  501. ('trunk/src@2', 'src'),
  502. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  503. ('trunk/other@2', 'src/other'))
  504. tree['src/file/other/DEPS'] = (
  505. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  506. tree['src/svn_hooked1'] = 'svn_hooked1'
  507. tree['src/svn_hooked2'] = 'svn_hooked2'
  508. self.assertTree(tree)
  509. out = self.parseGclient(['status', '--deps', 'mac', '--jobs', '1'],
  510. [['running', join(self.root_dir, 'src')]])
  511. out = self.svnBlockCleanup(out)
  512. self.checkString('file', out[0][1])
  513. self.checkString('other', out[0][2])
  514. self.checkString('svn_hooked1', out[0][3])
  515. self.checkString('svn_hooked2', out[0][4])
  516. self.checkString(join('third_party', 'foo'), out[0][5])
  517. self.assertEquals(6, len(out[0]))
  518. self.assertEquals(1, len(out))
  519. def testRevertAndStatusDepsOs(self):
  520. if not self.enabled:
  521. return
  522. self.gclient(['config', self.svn_base + 'trunk/src/'])
  523. # Tested in testSync.
  524. self.gclient(['sync', '--deps', 'mac', '--revision', 'src@1'])
  525. write(join(self.root_dir, 'src', 'other', 'hi'), 'Hey!')
  526. # Without --verbose, gclient won't output the directories without
  527. # modification.
  528. out = self.parseGclient(['status', '--deps', 'mac', '--jobs', '1'],
  529. [['running', join(self.root_dir, 'src')],
  530. ['running', join(self.root_dir, 'src', 'other')]])
  531. out = self.svnBlockCleanup(out)
  532. self.checkString('other', out[0][1])
  533. self.checkString(join('third_party', 'fpp'), out[0][2])
  534. self.checkString(join('third_party', 'prout'), out[0][3])
  535. self.checkString('hi', out[1][1])
  536. self.assertEquals(4, len(out[0]))
  537. self.assertEquals(2, len(out[1]))
  538. # So verify it works with --verbose.
  539. out = self.parseGclient(
  540. ['status', '--deps', 'mac', '--verbose', '--jobs', '1'],
  541. [['running', join(self.root_dir, 'src')],
  542. ['running', join(self.root_dir, 'src', 'third_party', 'fpp')],
  543. ['running', join(self.root_dir, 'src', 'other')],
  544. ['running', join(self.root_dir, 'src', 'third_party', 'prout')]])
  545. out = self.svnBlockCleanup(out)
  546. self.checkString('other', out[0][1])
  547. self.checkString(join('third_party', 'fpp'), out[0][2])
  548. self.checkString(join('third_party', 'prout'), out[0][3])
  549. self.checkString('hi', out[2][1])
  550. self.assertEquals(4, len(out[0]))
  551. self.assertEquals(1, len(out[1]))
  552. self.assertEquals(2, len(out[2]))
  553. self.assertEquals(1, len(out[3]))
  554. self.assertEquals(4, len(out))
  555. # Revert implies --force implies running hooks without looking at pattern
  556. # matching.
  557. # TODO(maruel): In general, gclient revert output is wrong. It should output
  558. # the file list after some ___ running 'svn status'
  559. results = self.gclient(['revert', '--deps', 'mac', '--jobs', '1'])
  560. out = self.splitBlock(results[0])
  561. self.assertEquals(7, len(out))
  562. self.checkString('', results[1])
  563. self.assertEquals(0, results[2])
  564. tree = self.mangle_svn_tree(
  565. ('trunk/src@1', 'src'),
  566. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  567. ('trunk/other@1', 'src/other'),
  568. ('trunk/third_party/prout@2', 'src/third_party/prout'))
  569. self.assertTree(tree)
  570. out = self.parseGclient(['status', '--deps', 'mac', '--jobs', '1'],
  571. [['running', join(self.root_dir, 'src')]])
  572. out = self.svnBlockCleanup(out)
  573. self.checkString('other', out[0][1])
  574. self.checkString(join('third_party', 'fpp'), out[0][2])
  575. self.checkString(join('third_party', 'prout'), out[0][3])
  576. self.assertEquals(4, len(out[0]))
  577. def testRunHooks(self):
  578. if not self.enabled:
  579. return
  580. self.gclient(['config', self.svn_base + 'trunk/src/'])
  581. self.gclient(['sync', '--deps', 'mac'])
  582. out = self.parseGclient(['runhooks', '--deps', 'mac'],
  583. ['running', 'running'])
  584. self.checkString(1, len(out[0]))
  585. self.checkString(1, len(out[1]))
  586. def testRunHooksDepsOs(self):
  587. if not self.enabled:
  588. return
  589. self.gclient(['config', self.svn_base + 'trunk/src/'])
  590. self.gclient(['sync', '--deps', 'mac', '--revision', 'src@1'])
  591. out = self.parseGclient(['runhooks', '--deps', 'mac'], [])
  592. self.assertEquals([], out)
  593. def testRevInfo(self):
  594. if not self.enabled:
  595. return
  596. self.gclient(['config', self.svn_base + 'trunk/src/'])
  597. self.gclient(['sync', '--deps', 'mac'])
  598. results = self.gclient(['revinfo', '--deps', 'mac'])
  599. out = ('src: %(base)s/src\n'
  600. 'src/file/other: File("%(base)s/other/DEPS")\n'
  601. 'src/other: %(base)s/other\n'
  602. 'src/third_party/foo: %(base)s/third_party/foo@1\n' %
  603. { 'base': self.svn_base + 'trunk' })
  604. self.check((out, '', 0), results)
  605. results = self.gclient(['revinfo', '--deps', 'mac', '--actual'])
  606. out = ('src: %(base)s/src@2\n'
  607. 'src/file/other: %(base)s/other/DEPS@2\n'
  608. 'src/other: %(base)s/other@2\n'
  609. 'src/third_party/foo: %(base)s/third_party/foo@1\n' %
  610. { 'base': self.svn_base + 'trunk' })
  611. self.check((out, '', 0), results)
  612. results = self.gclient(['revinfo', '--deps', 'mac', '--snapshot'])
  613. out = ('# Snapshot generated with gclient revinfo --snapshot\n'
  614. 'solutions = [\n'
  615. ' { "name" : "src",\n'
  616. ' "url" : "%(base)s/src",\n'
  617. ' "custom_deps" : {\n'
  618. ' "foo/bar": None,\n'
  619. ' "invalid": None,\n'
  620. ' "src/file/other": \'%(base)s/other/DEPS@2\',\n'
  621. ' "src/other": \'%(base)s/other@2\',\n'
  622. ' "src/third_party/foo": '
  623. '\'%(base)s/third_party/foo@1\',\n'
  624. ' },\n'
  625. ' "safesync_url": "",\n'
  626. ' },\n'
  627. ']\n\n' %
  628. { 'base': self.svn_base + 'trunk' })
  629. self.check((out, '', 0), results)
  630. def testWrongDirectory(self):
  631. # Check that we're not using a .gclient configuration which only talks
  632. # about a subdirectory src when we're in a different subdirectory src-other.
  633. if not self.enabled:
  634. return
  635. self.gclient(['config', self.svn_base + 'trunk/src/'])
  636. self.gclient(['sync'])
  637. other_src = join(self.root_dir, 'src-other')
  638. os.mkdir(other_src)
  639. res = ('', 'Error: client not configured; see \'gclient config\'\n', 1)
  640. self.check(res, self.gclient(['status'], other_src))
  641. def testCorrectDirectory(self):
  642. # Check that when we're in the subdirectory src, the .gclient configuration
  643. # is used.
  644. if not self.enabled:
  645. return
  646. self.gclient(['config', self.svn_base + 'trunk/src/'])
  647. self.gclient(['sync'])
  648. src = join(self.root_dir, 'src')
  649. res = self.gclient(['status', '--jobs', '1'], src)
  650. self.checkBlock(res[0], [('running', src)])
  651. def testInitialCheckoutNotYetDone(self):
  652. # Check that gclient can be executed when the initial checkout hasn't been
  653. # done yet.
  654. if not self.enabled:
  655. return
  656. self.gclient(['config', self.svn_base + 'trunk/src/'])
  657. self.parseGclient(
  658. ['sync', '--jobs', '1'],
  659. ['running', 'running',
  660. # This is due to the way svn update is called for a
  661. # single file when File() is used in a DEPS file.
  662. ('running', os.path.join(self.root_dir, 'src', 'file', 'other')),
  663. 'running', 'running', 'running', 'running'])
  664. def testInitialCheckoutFailed(self):
  665. # Check that gclient can be executed from an arbitrary sub directory if the
  666. # initial checkout has failed.
  667. if not self.enabled:
  668. return
  669. self.gclient(['config', self.svn_base + 'trunk/src/'])
  670. self.gclient(['sync'])
  671. # Cripple the checkout.
  672. os.remove(join(self.root_dir, '.gclient_entries'))
  673. src = join(self.root_dir, 'src')
  674. res = self.gclient(['sync', '--jobs', '1'], src)
  675. self.checkBlock(res[0],
  676. ['running', 'running', 'running'])
  677. class GClientSmokeGIT(GClientSmokeBase):
  678. def setUp(self):
  679. super(GClientSmokeGIT, self).setUp()
  680. self.enabled = self.FAKE_REPOS.set_up_git()
  681. def testSync(self):
  682. if not self.enabled:
  683. return
  684. # TODO(maruel): safesync.
  685. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  686. # Test unversioned checkout.
  687. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1'],
  688. ['running', 'running', 'running', 'running', 'running'])
  689. # TODO(maruel): http://crosbug.com/3582 hooks run even if not matching, must
  690. # add sync parsing to get the list of updated files.
  691. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  692. ('repo_2@1', 'src/repo2'),
  693. ('repo_3@2', 'src/repo2/repo_renamed'))
  694. tree['src/git_hooked1'] = 'git_hooked1'
  695. tree['src/git_hooked2'] = 'git_hooked2'
  696. self.assertTree(tree)
  697. # Manually remove git_hooked1 before synching to make sure it's not
  698. # recreated.
  699. os.remove(join(self.root_dir, 'src', 'git_hooked1'))
  700. # Test incremental versioned sync: sync backward.
  701. self.parseGclient(['sync', '--jobs', '1', '--revision',
  702. 'src@' + self.githash('repo_1', 1),
  703. '--deps', 'mac', '--delete_unversioned_trees'],
  704. ['running', 'running', 'deleting'])
  705. tree = self.mangle_git_tree(('repo_1@1', 'src'),
  706. ('repo_2@2', 'src/repo2'),
  707. ('repo_3@1', 'src/repo2/repo3'),
  708. ('repo_4@2', 'src/repo4'))
  709. tree['src/git_hooked2'] = 'git_hooked2'
  710. self.assertTree(tree)
  711. # Test incremental sync: delete-unversioned_trees isn't there.
  712. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1'],
  713. ['running', 'running', 'running'])
  714. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  715. ('repo_2@1', 'src/repo2'),
  716. ('repo_3@1', 'src/repo2/repo3'),
  717. ('repo_3@2', 'src/repo2/repo_renamed'),
  718. ('repo_4@2', 'src/repo4'))
  719. tree['src/git_hooked1'] = 'git_hooked1'
  720. tree['src/git_hooked2'] = 'git_hooked2'
  721. self.assertTree(tree)
  722. def testSyncIgnoredSolutionName(self):
  723. """TODO(maruel): This will become an error soon."""
  724. if not self.enabled:
  725. return
  726. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  727. self.parseGclient(
  728. ['sync', '--deps', 'mac', '--jobs', '1',
  729. '--revision', 'invalid@' + self.githash('repo_1', 1)],
  730. ['running', 'running', 'running', 'running', 'running'],
  731. 'Please fix your script, having invalid --revision flags '
  732. 'will soon considered an error.\n')
  733. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  734. ('repo_2@1', 'src/repo2'),
  735. ('repo_3@2', 'src/repo2/repo_renamed'))
  736. tree['src/git_hooked1'] = 'git_hooked1'
  737. tree['src/git_hooked2'] = 'git_hooked2'
  738. self.assertTree(tree)
  739. def testSyncNoSolutionName(self):
  740. if not self.enabled:
  741. return
  742. # When no solution name is provided, gclient uses the first solution listed.
  743. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  744. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1',
  745. '--revision', self.githash('repo_1', 1)],
  746. ['running', 'running', 'running', 'running'])
  747. tree = self.mangle_git_tree(('repo_1@1', 'src'),
  748. ('repo_2@2', 'src/repo2'),
  749. ('repo_3@1', 'src/repo2/repo3'),
  750. ('repo_4@2', 'src/repo4'))
  751. self.assertTree(tree)
  752. def testSyncJobs(self):
  753. if not self.enabled:
  754. return
  755. # TODO(maruel): safesync.
  756. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  757. # Test unversioned checkout.
  758. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '8'],
  759. ['running', 'running', 'running', 'running', 'running'],
  760. untangle=True)
  761. # TODO(maruel): http://crosbug.com/3582 hooks run even if not matching, must
  762. # add sync parsing to get the list of updated files.
  763. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  764. ('repo_2@1', 'src/repo2'),
  765. ('repo_3@2', 'src/repo2/repo_renamed'))
  766. tree['src/git_hooked1'] = 'git_hooked1'
  767. tree['src/git_hooked2'] = 'git_hooked2'
  768. self.assertTree(tree)
  769. # Manually remove git_hooked1 before synching to make sure it's not
  770. # recreated.
  771. os.remove(join(self.root_dir, 'src', 'git_hooked1'))
  772. # Test incremental versioned sync: sync backward.
  773. self.parseGclient(
  774. ['sync', '--revision', 'src@' + self.githash('repo_1', 1),
  775. '--deps', 'mac', '--delete_unversioned_trees', '--jobs', '8'],
  776. ['running', 'running', 'deleting'],
  777. untangle=True)
  778. tree = self.mangle_git_tree(('repo_1@1', 'src'),
  779. ('repo_2@2', 'src/repo2'),
  780. ('repo_3@1', 'src/repo2/repo3'),
  781. ('repo_4@2', 'src/repo4'))
  782. tree['src/git_hooked2'] = 'git_hooked2'
  783. self.assertTree(tree)
  784. # Test incremental sync: delete-unversioned_trees isn't there.
  785. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '8'],
  786. ['running', 'running', 'running'], untangle=True)
  787. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  788. ('repo_2@1', 'src/repo2'),
  789. ('repo_3@1', 'src/repo2/repo3'),
  790. ('repo_3@2', 'src/repo2/repo_renamed'),
  791. ('repo_4@2', 'src/repo4'))
  792. tree['src/git_hooked1'] = 'git_hooked1'
  793. tree['src/git_hooked2'] = 'git_hooked2'
  794. self.assertTree(tree)
  795. def testRevertAndStatus(self):
  796. """TODO(maruel): Remove this line once this test is fixed."""
  797. if not self.enabled:
  798. return
  799. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  800. # Tested in testSync.
  801. self.gclient(['sync', '--deps', 'mac'])
  802. write(join(self.root_dir, 'src', 'repo2', 'hi'), 'Hey!')
  803. out = self.parseGclient(['status', '--deps', 'mac'], [])
  804. # TODO(maruel): http://crosbug.com/3584 It should output the unversioned
  805. # files.
  806. self.assertEquals(0, len(out))
  807. # Revert implies --force implies running hooks without looking at pattern
  808. # matching.
  809. results = self.gclient(['revert', '--deps', 'mac'])
  810. out = results[0].splitlines(False)
  811. # TODO(maruel): http://crosbug.com/3583 It just runs the hooks right now.
  812. self.assertEquals(13, len(out))
  813. self.checkString('', results[1])
  814. self.assertEquals(0, results[2])
  815. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  816. ('repo_2@1', 'src/repo2'),
  817. ('repo_3@2', 'src/repo2/repo_renamed'))
  818. # TODO(maruel): http://crosbug.com/3583 This file should have been removed.
  819. tree[join('src', 'repo2', 'hi')] = 'Hey!'
  820. tree['src/git_hooked1'] = 'git_hooked1'
  821. tree['src/git_hooked2'] = 'git_hooked2'
  822. self.assertTree(tree)
  823. results = self.gclient(['status', '--deps', 'mac'])
  824. out = results[0].splitlines(False)
  825. # TODO(maruel): http://crosbug.com/3584 It should output the unversioned
  826. # files.
  827. self.assertEquals(0, len(out))
  828. def testRunHooks(self):
  829. if not self.enabled:
  830. return
  831. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  832. self.gclient(['sync', '--deps', 'mac'])
  833. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  834. ('repo_2@1', 'src/repo2'),
  835. ('repo_3@2', 'src/repo2/repo_renamed'))
  836. tree['src/git_hooked1'] = 'git_hooked1'
  837. tree['src/git_hooked2'] = 'git_hooked2'
  838. self.assertTree(tree)
  839. os.remove(join(self.root_dir, 'src', 'git_hooked1'))
  840. os.remove(join(self.root_dir, 'src', 'git_hooked2'))
  841. # runhooks runs all hooks even if not matching by design.
  842. out = self.parseGclient(['runhooks', '--deps', 'mac'],
  843. ['running', 'running'])
  844. self.assertEquals(1, len(out[0]))
  845. self.assertEquals(1, len(out[1]))
  846. tree = self.mangle_git_tree(('repo_1@2', 'src'),
  847. ('repo_2@1', 'src/repo2'),
  848. ('repo_3@2', 'src/repo2/repo_renamed'))
  849. tree['src/git_hooked1'] = 'git_hooked1'
  850. tree['src/git_hooked2'] = 'git_hooked2'
  851. self.assertTree(tree)
  852. def testRevInfo(self):
  853. if not self.enabled:
  854. return
  855. self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
  856. self.gclient(['sync', '--deps', 'mac'])
  857. results = self.gclient(['revinfo', '--deps', 'mac'])
  858. out = ('src: %(base)srepo_1\n'
  859. 'src/repo2: %(base)srepo_2@%(hash2)s\n'
  860. 'src/repo2/repo_renamed: %(base)srepo_3\n' %
  861. {
  862. 'base': self.git_base,
  863. 'hash2': self.githash('repo_2', 1)[:7],
  864. })
  865. self.check((out, '', 0), results)
  866. results = self.gclient(['revinfo', '--deps', 'mac', '--actual'])
  867. out = ('src: %(base)srepo_1@%(hash1)s\n'
  868. 'src/repo2: %(base)srepo_2@%(hash2)s\n'
  869. 'src/repo2/repo_renamed: %(base)srepo_3@%(hash3)s\n' %
  870. {
  871. 'base': self.git_base,
  872. 'hash1': self.githash('repo_1', 2),
  873. 'hash2': self.githash('repo_2', 1),
  874. 'hash3': self.githash('repo_3', 2),
  875. })
  876. self.check((out, '', 0), results)
  877. class GClientSmokeBoth(GClientSmokeBase):
  878. def setUp(self):
  879. super(GClientSmokeBoth, self).setUp()
  880. self.enabled = self.FAKE_REPOS.set_up_svn() and self.FAKE_REPOS.set_up_git()
  881. def testMultiSolutions(self):
  882. if not self.enabled:
  883. return
  884. self.gclient(['config', '--spec',
  885. 'solutions=['
  886. '{"name": "src",'
  887. ' "url": "' + self.svn_base + 'trunk/src/"},'
  888. '{"name": "src-git",'
  889. '"url": "' + self.git_base + 'repo_1"}]'])
  890. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1'],
  891. ['running', 'running', 'running',
  892. # This is due to the way svn update is called for a single
  893. # file when File() is used in a DEPS file.
  894. ('running', self.root_dir + '/src/file/other'),
  895. 'running', 'running', 'running', 'running', 'running', 'running',
  896. 'running', 'running'])
  897. tree = self.mangle_git_tree(('repo_1@2', 'src-git'),
  898. ('repo_2@1', 'src/repo2'),
  899. ('repo_3@2', 'src/repo2/repo_renamed'))
  900. tree.update(self.mangle_svn_tree(
  901. ('trunk/src@2', 'src'),
  902. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  903. ('trunk/other@2', 'src/other')))
  904. tree['src/file/other/DEPS'] = (
  905. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  906. tree['src/git_hooked1'] = 'git_hooked1'
  907. tree['src/git_hooked2'] = 'git_hooked2'
  908. tree['src/svn_hooked1'] = 'svn_hooked1'
  909. self.assertTree(tree)
  910. def testMultiSolutionsJobs(self):
  911. if not self.enabled:
  912. return
  913. self.gclient(['config', '--spec',
  914. 'solutions=['
  915. '{"name": "src",'
  916. ' "url": "' + self.svn_base + 'trunk/src/"},'
  917. '{"name": "src-git",'
  918. '"url": "' + self.git_base + 'repo_1"}]'])
  919. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '8'],
  920. ['running', 'running', 'running',
  921. # This is due to the way svn update is called for a single
  922. # file when File() is used in a DEPS file.
  923. ('running', self.root_dir + '/src/file/other'),
  924. 'running', 'running', 'running', 'running', 'running', 'running',
  925. 'running', 'running'],
  926. untangle=True)
  927. tree = self.mangle_git_tree(('repo_1@2', 'src-git'),
  928. ('repo_2@1', 'src/repo2'),
  929. ('repo_3@2', 'src/repo2/repo_renamed'))
  930. tree.update(self.mangle_svn_tree(
  931. ('trunk/src@2', 'src'),
  932. ('trunk/third_party/foo@1', 'src/third_party/foo'),
  933. ('trunk/other@2', 'src/other')))
  934. tree['src/file/other/DEPS'] = (
  935. self.FAKE_REPOS.svn_revs[2]['trunk/other/DEPS'])
  936. tree['src/git_hooked1'] = 'git_hooked1'
  937. tree['src/git_hooked2'] = 'git_hooked2'
  938. tree['src/svn_hooked1'] = 'svn_hooked1'
  939. self.assertTree(tree)
  940. def testMultiSolutionsMultiRev(self):
  941. if not self.enabled:
  942. return
  943. self.gclient(['config', '--spec',
  944. 'solutions=['
  945. '{"name": "src",'
  946. ' "url": "' + self.svn_base + 'trunk/src/"},'
  947. '{"name": "src-git",'
  948. '"url": "' + self.git_base + 'repo_1"}]'])
  949. self.parseGclient(
  950. ['sync', '--deps', 'mac', '--jobs', '1', '--revision', '1',
  951. '-r', 'src-git@' + self.githash('repo_1', 1)],
  952. ['running', 'running', 'running', 'running',
  953. 'running', 'running', 'running', 'running'])
  954. tree = self.mangle_git_tree(('repo_1@1', 'src-git'),
  955. ('repo_2@2', 'src/repo2'),
  956. ('repo_3@1', 'src/repo2/repo3'),
  957. ('repo_4@2', 'src/repo4'))
  958. tree.update(self.mangle_svn_tree(
  959. ('trunk/src@1', 'src'),
  960. ('trunk/third_party/foo@2', 'src/third_party/fpp'),
  961. ('trunk/other@1', 'src/other'),
  962. ('trunk/third_party/foo@2', 'src/third_party/prout')))
  963. self.assertTree(tree)
  964. def testRevInfo(self):
  965. if not self.enabled:
  966. return
  967. self.gclient(['config', '--spec',
  968. 'solutions=['
  969. '{"name": "src",'
  970. ' "url": "' + self.svn_base + 'trunk/src/"},'
  971. '{"name": "src-git",'
  972. '"url": "' + self.git_base + 'repo_1"}]'])
  973. self.gclient(['sync', '--deps', 'mac'])
  974. results = self.gclient(['revinfo', '--deps', 'mac'])
  975. out = ('src: %(svn_base)s/src/\n'
  976. 'src-git: %(git_base)srepo_1\n'
  977. 'src/file/other: File("%(svn_base)s/other/DEPS")\n'
  978. 'src/other: %(svn_base)s/other\n'
  979. 'src/repo2: %(git_base)srepo_2@%(hash2)s\n'
  980. 'src/repo2/repo_renamed: %(git_base)srepo_3\n'
  981. 'src/third_party/foo: %(svn_base)s/third_party/foo@1\n') % {
  982. 'svn_base': self.svn_base + 'trunk',
  983. 'git_base': self.git_base,
  984. 'hash2': self.githash('repo_2', 1)[:7],
  985. }
  986. self.check((out, '', 0), results)
  987. results = self.gclient(['revinfo', '--deps', 'mac', '--actual'])
  988. out = ('src: %(svn_base)s/src/@2\n'
  989. 'src-git: %(git_base)srepo_1@%(hash1)s\n'
  990. 'src/file/other: %(svn_base)s/other/DEPS@2\n'
  991. 'src/other: %(svn_base)s/other@2\n'
  992. 'src/repo2: %(git_base)srepo_2@%(hash2)s\n'
  993. 'src/repo2/repo_renamed: %(git_base)srepo_3@%(hash3)s\n'
  994. 'src/third_party/foo: %(svn_base)s/third_party/foo@1\n') % {
  995. 'svn_base': self.svn_base + 'trunk',
  996. 'git_base': self.git_base,
  997. 'hash1': self.githash('repo_1', 2),
  998. 'hash2': self.githash('repo_2', 1),
  999. 'hash3': self.githash('repo_3', 2),
  1000. }
  1001. self.check((out, '', 0), results)
  1002. def testRecurse(self):
  1003. if not self.enabled:
  1004. return
  1005. self.gclient(['config', '--spec',
  1006. 'solutions=['
  1007. '{"name": "src",'
  1008. ' "url": "' + self.svn_base + 'trunk/src/"},'
  1009. '{"name": "src-git",'
  1010. '"url": "' + self.git_base + 'repo_1"}]'])
  1011. self.gclient(['sync', '--deps', 'mac'])
  1012. results = self.gclient(['recurse', 'sh', '-c',
  1013. 'echo $GCLIENT_SCM,$GCLIENT_URL,`pwd`'])
  1014. entries = [tuple(line.split(','))
  1015. for line in results[0].strip().split('\n')]
  1016. logging.debug(entries)
  1017. bases = {'svn': self.svn_base, 'git': self.git_base}
  1018. expected_source = [
  1019. ('svn', 'trunk/src/', 'src'),
  1020. ('git', 'repo_1', 'src-git'),
  1021. ('svn', 'trunk/other', 'src/other'),
  1022. ('git', 'repo_2@' + self.githash('repo_2', 1)[:7], 'src/repo2'),
  1023. ('git', 'repo_3', 'src/repo2/repo_renamed'),
  1024. ('svn', 'trunk/third_party/foo@1', 'src/third_party/foo'),
  1025. ]
  1026. expected = [(scm, bases[scm] + url, os.path.join(self.root_dir, path))
  1027. for (scm, url, path) in expected_source]
  1028. self.assertEquals(sorted(entries), sorted(expected))
  1029. class GClientSmokeFromCheckout(GClientSmokeBase):
  1030. # WebKit abuses this. It has a .gclient and a DEPS from a checkout.
  1031. def setUp(self):
  1032. super(GClientSmokeFromCheckout, self).setUp()
  1033. self.enabled = self.FAKE_REPOS.set_up_svn()
  1034. os.rmdir(self.root_dir)
  1035. if self.enabled:
  1036. usr, pwd = self.FAKE_REPOS.USERS[0]
  1037. subprocess2.check_call(
  1038. ['svn', 'checkout', self.svn_base + '/trunk/webkit',
  1039. self.root_dir, '-q',
  1040. '--non-interactive', '--no-auth-cache',
  1041. '--username', usr, '--password', pwd])
  1042. def testSync(self):
  1043. if not self.enabled:
  1044. return
  1045. self.parseGclient(['sync', '--deps', 'mac', '--jobs', '1'],
  1046. ['running', 'running'])
  1047. tree = self.mangle_svn_tree(
  1048. ('trunk/webkit@2', ''),
  1049. ('trunk/third_party/foo@1', 'foo/bar'))
  1050. self.assertTree(tree)
  1051. def testRevertAndStatus(self):
  1052. if not self.enabled:
  1053. return
  1054. self.gclient(['sync'])
  1055. # TODO(maruel): This is incorrect.
  1056. out = self.parseGclient(['status', '--deps', 'mac', '--jobs', '1'], [])
  1057. # Revert implies --force implies running hooks without looking at pattern
  1058. # matching.
  1059. results = self.gclient(['revert', '--deps', 'mac', '--jobs', '1'])
  1060. out = self.splitBlock(results[0])
  1061. self.assertEquals(2, len(out))
  1062. self.checkString(2, len(out[0]))
  1063. self.checkString(2, len(out[1]))
  1064. self.checkString('foo', out[1][1])
  1065. self.checkString('', results[1])
  1066. self.assertEquals(0, results[2])
  1067. tree = self.mangle_svn_tree(
  1068. ('trunk/webkit@2', ''),
  1069. ('trunk/third_party/foo@1', 'foo/bar'))
  1070. self.assertTree(tree)
  1071. # TODO(maruel): This is incorrect.
  1072. out = self.parseGclient(['status', '--deps', 'mac'], [])
  1073. def testRunHooks(self):
  1074. if not self.enabled:
  1075. return
  1076. # Hooks aren't really tested for now since there is no hook defined.
  1077. self.gclient(['sync', '--deps', 'mac'])
  1078. out = self.parseGclient(['runhooks', '--deps', 'mac'], ['running'])
  1079. self.assertEquals(1, len(out))
  1080. self.assertEquals(2, len(out[0]))
  1081. self.assertEquals(3, len(out[0][0]))
  1082. self.checkString('foo', out[0][1])
  1083. tree = self.mangle_svn_tree(
  1084. ('trunk/webkit@2', ''),
  1085. ('trunk/third_party/foo@1', 'foo/bar'))
  1086. self.assertTree(tree)
  1087. def testRevInfo(self):
  1088. if not self.enabled:
  1089. return
  1090. self.gclient(['sync', '--deps', 'mac'])
  1091. results = self.gclient(['revinfo', '--deps', 'mac'])
  1092. expected = (
  1093. './: None\nfoo/bar: %strunk/third_party/foo@1\n' % self.svn_base,
  1094. '', 0)
  1095. self.check(expected, results)
  1096. # TODO(maruel): To be added after the refactor.
  1097. #results = self.gclient(['revinfo', '--snapshot'])
  1098. #expected = (
  1099. # './: None\nfoo/bar: %strunk/third_party/foo@1\n' % self.svn_base,
  1100. # '', 0)
  1101. #self.check(expected, results)
  1102. def testRest(self):
  1103. if not self.enabled:
  1104. return
  1105. self.gclient(['sync'])
  1106. # TODO(maruel): This is incorrect, it should run on ./ too.
  1107. self.parseGclient(
  1108. ['cleanup', '--deps', 'mac', '--verbose', '--jobs', '1'],
  1109. [('running', join(self.root_dir, 'foo', 'bar'))])
  1110. self.parseGclient(
  1111. ['diff', '--deps', 'mac', '--verbose', '--jobs', '1'],
  1112. [('running', join(self.root_dir, 'foo', 'bar'))])
  1113. if __name__ == '__main__':
  1114. if '-v' in sys.argv:
  1115. logging.basicConfig(level=logging.DEBUG)
  1116. if '-c' in sys.argv:
  1117. COVERAGE = True
  1118. sys.argv.remove('-c')
  1119. if os.path.exists('.coverage'):
  1120. os.remove('.coverage')
  1121. os.environ['COVERAGE_FILE'] = os.path.join(
  1122. os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
  1123. '.coverage')
  1124. unittest.main()