checkout_test.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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 checkout.py."""
  6. import logging
  7. import os
  8. import shutil
  9. import sys
  10. import unittest
  11. from xml.etree import ElementTree
  12. ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
  13. sys.path.insert(0, os.path.dirname(ROOT_DIR))
  14. from testing_support import fake_repos
  15. from testing_support.patches_data import GIT, RAW
  16. import checkout
  17. import patch
  18. import subprocess2
  19. # pass -v to enable it.
  20. DEBUGGING = False
  21. # A patch that will fail to apply.
  22. BAD_PATCH = ''.join(
  23. [l for l in GIT.PATCH.splitlines(True) if l.strip() != 'e'])
  24. class FakeRepos(fake_repos.FakeReposBase):
  25. TEST_GIT_REPO = 'repo_1'
  26. def populateGit(self):
  27. """Creates a few revisions of changes files."""
  28. self._commit_git(self.TEST_GIT_REPO, self._git_tree())
  29. # Fix for the remote rejected error. For more details see:
  30. # http://stackoverflow.com/questions/2816369/git-push-error-remote
  31. subprocess2.check_output(
  32. ['git', '--git-dir',
  33. os.path.join(self.git_root, self.TEST_GIT_REPO, '.git'),
  34. 'config', '--bool', 'core.bare', 'true'])
  35. assert os.path.isdir(
  36. os.path.join(self.git_root, self.TEST_GIT_REPO, '.git'))
  37. @staticmethod
  38. def _git_tree():
  39. fs = {}
  40. fs['origin'] = 'git@1'
  41. fs['extra'] = 'dummy\n' # new
  42. fs['codereview.settings'] = (
  43. '# Test data\n'
  44. 'bar: pouet\n')
  45. fs['chrome/file.cc'] = (
  46. 'a\n'
  47. 'bb\n'
  48. 'ccc\n'
  49. 'dd\n'
  50. 'e\n'
  51. 'ff\n'
  52. 'ggg\n'
  53. 'hh\n'
  54. 'i\n'
  55. 'jj\n'
  56. 'kkk\n'
  57. 'll\n'
  58. 'm\n'
  59. 'nn\n'
  60. 'ooo\n'
  61. 'pp\n'
  62. 'q\n')
  63. fs['chromeos/views/DOMui_menu_widget.h'] = (
  64. '// Copyright (c) 2010\n'
  65. '// Use of this source code\n'
  66. '// found in the LICENSE file.\n'
  67. '\n'
  68. '#ifndef DOM\n'
  69. '#define DOM\n'
  70. '#pragma once\n'
  71. '\n'
  72. '#include <string>\n'
  73. '#endif\n')
  74. return fs
  75. # pylint: disable=no-self-use
  76. class BaseTest(fake_repos.FakeReposTestBase):
  77. name = 'foo'
  78. FAKE_REPOS_CLASS = FakeRepos
  79. is_read_only = False
  80. def setUp(self):
  81. super(BaseTest, self).setUp()
  82. self._old_call = subprocess2.call
  83. def redirect_call(args, **kwargs):
  84. if not DEBUGGING:
  85. kwargs.setdefault('stdout', subprocess2.PIPE)
  86. kwargs.setdefault('stderr', subprocess2.STDOUT)
  87. return self._old_call(args, **kwargs)
  88. subprocess2.call = redirect_call
  89. self.usr, self.pwd = self.FAKE_REPOS.USERS[0]
  90. self.previous_log = None
  91. def tearDown(self):
  92. subprocess2.call = self._old_call
  93. super(BaseTest, self).tearDown()
  94. def get_patches(self):
  95. return patch.PatchSet([
  96. patch.FilePatchDiff('new_dir/subdir/new_file', GIT.NEW_SUBDIR, []),
  97. patch.FilePatchDiff('chrome/file.cc', GIT.PATCH, []),
  98. # TODO(maruel): Test with is_new == False.
  99. patch.FilePatchBinary('bin_file', '\x00', [], is_new=True),
  100. patch.FilePatchDelete('extra', False),
  101. ])
  102. def get_trunk(self, modified):
  103. raise NotImplementedError()
  104. def _check_base(self, co, root, expected):
  105. raise NotImplementedError()
  106. def _check_exception(self, co, err_msg):
  107. co.prepare(None)
  108. try:
  109. co.apply_patch([patch.FilePatchDiff('chrome/file.cc', BAD_PATCH, [])])
  110. self.fail()
  111. except checkout.PatchApplicationFailed, e:
  112. self.assertEquals(e.filename, 'chrome/file.cc')
  113. self.assertEquals(e.status, err_msg)
  114. def _log(self):
  115. raise NotImplementedError()
  116. def _test_process(self, co_lambda):
  117. """Makes sure the process lambda is called correctly."""
  118. post_processors = [lambda *args: results.append(args)]
  119. co = co_lambda(post_processors)
  120. self.assertEquals(post_processors, co.post_processors)
  121. co.prepare(None)
  122. ps = self.get_patches()
  123. results = []
  124. co.apply_patch(ps)
  125. expected_co = getattr(co, 'checkout', co)
  126. # Because of ReadOnlyCheckout.
  127. expected = [(expected_co, p) for p in ps.patches]
  128. self.assertEquals(len(expected), len(results))
  129. self.assertEquals(expected, results)
  130. def _check_move(self, co):
  131. """Makes sure file moves are handled correctly."""
  132. co.prepare(None)
  133. patchset = patch.PatchSet([
  134. patch.FilePatchDelete('chromeos/views/DOMui_menu_widget.h', False),
  135. patch.FilePatchDiff(
  136. 'chromeos/views/webui_menu_widget.h', GIT.RENAME_PARTIAL, []),
  137. ])
  138. co.apply_patch(patchset)
  139. # Make sure chromeos/views/DOMui_menu_widget.h is deleted and
  140. # chromeos/views/webui_menu_widget.h is correctly created.
  141. root = os.path.join(self.root_dir, self.name)
  142. tree = self.get_trunk(False)
  143. del tree['chromeos/views/DOMui_menu_widget.h']
  144. tree['chromeos/views/webui_menu_widget.h'] = (
  145. '// Copyright (c) 2011\n'
  146. '// Use of this source code\n'
  147. '// found in the LICENSE file.\n'
  148. '\n'
  149. '#ifndef WEB\n'
  150. '#define WEB\n'
  151. '#pragma once\n'
  152. '\n'
  153. '#include <string>\n'
  154. '#endif\n')
  155. #print patchset[0].get()
  156. #print fake_repos.read_tree(root)
  157. self.assertTree(tree, root)
  158. class GitBaseTest(BaseTest):
  159. def setUp(self):
  160. super(GitBaseTest, self).setUp()
  161. self.enabled = self.FAKE_REPOS.set_up_git()
  162. self.assertTrue(self.enabled)
  163. self.previous_log = self._log()
  164. # pylint: disable=arguments-differ
  165. def _log(self, log_from_local_repo=False):
  166. if log_from_local_repo:
  167. repo_root = os.path.join(self.root_dir, self.name)
  168. else:
  169. repo_root = os.path.join(self.FAKE_REPOS.git_root,
  170. self.FAKE_REPOS.TEST_GIT_REPO)
  171. out = subprocess2.check_output(
  172. ['git',
  173. '--git-dir',
  174. os.path.join(repo_root, '.git'),
  175. 'log', '--pretty=format:"%H%x09%ae%x09%ad%x09%s"',
  176. '--max-count=1']).strip('"')
  177. if out and len(out.split()) != 0:
  178. revision = out.split()[0]
  179. else:
  180. return {'revision': 0}
  181. return {
  182. 'revision': revision,
  183. 'author': out.split()[1],
  184. 'msg': out.split()[-1],
  185. }
  186. def _check_base(self, co, root, expected):
  187. read_only = isinstance(co, checkout.ReadOnlyCheckout)
  188. self.assertEquals(read_only, self.is_read_only)
  189. if not read_only:
  190. self.FAKE_REPOS.git_dirty = True
  191. self.assertEquals(root, co.project_path)
  192. git_rev = co.prepare(None)
  193. self.assertEquals(unicode, type(git_rev))
  194. self.assertEquals(self.previous_log['revision'], git_rev)
  195. self.assertEquals('pouet', co.get_settings('bar'))
  196. self.assertTree(self.get_trunk(False), root)
  197. patches = self.get_patches()
  198. co.apply_patch(patches)
  199. self.assertEquals(
  200. ['bin_file', 'chrome/file.cc', 'new_dir/subdir/new_file', 'extra'],
  201. patches.filenames)
  202. # Hackish to verify _branches() internal function.
  203. # pylint: disable=protected-access
  204. self.assertEquals(
  205. (['master', 'working_branch'], 'working_branch'),
  206. co._branches())
  207. # Verify that the patch is applied even for read only checkout.
  208. self.assertTree(self.get_trunk(True), root)
  209. fake_author = self.FAKE_REPOS.USERS[1][0]
  210. revision = co.commit(u'msg', fake_author)
  211. # Nothing changed.
  212. self.assertTree(self.get_trunk(True), root)
  213. if read_only:
  214. self.assertEquals('FAKE', revision)
  215. self.assertEquals(self.previous_log['revision'], co.prepare(None))
  216. # Changes should be reverted now.
  217. self.assertTree(self.get_trunk(False), root)
  218. expected = self.previous_log
  219. else:
  220. self.assertEquals(self._log()['revision'], revision)
  221. self.assertEquals(self._log()['revision'], co.prepare(None))
  222. self.assertTree(self.get_trunk(True), root)
  223. expected = self._log()
  224. actual = self._log(log_from_local_repo=True)
  225. self.assertEquals(expected, actual)
  226. def get_trunk(self, modified):
  227. tree = {}
  228. for k, v in self.FAKE_REPOS.git_hashes[
  229. self.FAKE_REPOS.TEST_GIT_REPO][1][1].iteritems():
  230. assert k not in tree
  231. tree[k] = v
  232. if modified:
  233. content_lines = tree['chrome/file.cc'].splitlines(True)
  234. tree['chrome/file.cc'] = ''.join(
  235. content_lines[0:5] + ['FOO!\n'] + content_lines[5:])
  236. tree['bin_file'] = '\x00'
  237. del tree['extra']
  238. tree['new_dir/subdir/new_file'] = 'A new file\nshould exist.\n'
  239. return tree
  240. def _test_prepare(self, co):
  241. print co.prepare(None)
  242. class GitCheckout(GitBaseTest):
  243. def _get_co(self, post_processors):
  244. self.assertNotEqual(False, post_processors)
  245. return checkout.GitCheckout(
  246. root_dir=self.root_dir,
  247. project_name=self.name,
  248. remote_branch='master',
  249. git_url=os.path.join(self.FAKE_REPOS.git_root,
  250. self.FAKE_REPOS.TEST_GIT_REPO),
  251. commit_user=self.usr,
  252. post_processors=post_processors)
  253. def testAll(self):
  254. root = os.path.join(self.root_dir, self.name)
  255. self._check_base(self._get_co(None), root, None)
  256. @unittest.skip('flaky')
  257. def testException(self):
  258. self._check_exception(
  259. self._get_co(None),
  260. 'While running git apply --index -3 -p1;\n fatal: corrupt patch at '
  261. 'line 12\n')
  262. def testProcess(self):
  263. self._test_process(self._get_co)
  264. def _testPrepare(self):
  265. self._test_prepare(self._get_co(None))
  266. def testMove(self):
  267. co = self._get_co(None)
  268. self._check_move(co)
  269. out = subprocess2.check_output(
  270. ['git', 'diff', '--staged', '--name-status', '--no-renames'],
  271. cwd=co.project_path)
  272. out = sorted(out.splitlines())
  273. expected = sorted(
  274. [
  275. 'A\tchromeos/views/webui_menu_widget.h',
  276. 'D\tchromeos/views/DOMui_menu_widget.h',
  277. ])
  278. self.assertEquals(expected, out)
  279. if __name__ == '__main__':
  280. if '-v' in sys.argv:
  281. DEBUGGING = True
  282. logging.basicConfig(
  283. level=logging.DEBUG,
  284. format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
  285. else:
  286. logging.basicConfig(
  287. level=logging.ERROR,
  288. format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
  289. unittest.main()