Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: tests/checkout_test.py

Issue 6877055: Move commit-queue/checkout into depot_tools so it can be reused by the try server. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: .gitignore Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « checkout.py ('k') | tests/sample_pre_commit_hook » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
6 """Unit tests for checkout.py."""
7
8 from __future__ import with_statement
9 import logging
10 import os
11 import shutil
12 import sys
13 import unittest
14 from xml.etree import ElementTree
15
16 ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
17 BASE_DIR = os.path.join(ROOT_DIR, '..')
18 sys.path.insert(0, BASE_DIR)
19
20 import checkout
21 import patch
22 import subprocess2
23 from tests import fake_repos
24
25
26 # pass -v to enable it.
27 DEBUGGING = False
28
29 # A naked patch.
30 NAKED_PATCH = ("""\
31 --- svn_utils_test.txt
32 +++ svn_utils_test.txt
33 @@ -3,6 +3,7 @@ bb
34 ccc
35 dd
36 e
37 +FOO!
38 ff
39 ggg
40 hh
41 """)
42
43 # A patch generated from git.
44 GIT_PATCH = ("""\
45 diff --git a/svn_utils_test.txt b/svn_utils_test.txt
46 index 0e4de76..8320059 100644
47 --- a/svn_utils_test.txt
48 +++ b/svn_utils_test.txt
49 @@ -3,6 +3,7 @@ bb
50 ccc
51 dd
52 e
53 +FOO!
54 ff
55 ggg
56 hh
57 """)
58
59 # A patch that will fail to apply.
60 BAD_PATCH = ("""\
61 diff --git a/svn_utils_test.txt b/svn_utils_test.txt
62 index 0e4de76..8320059 100644
63 --- a/svn_utils_test.txt
64 +++ b/svn_utils_test.txt
65 @@ -3,7 +3,8 @@ bb
66 ccc
67 dd
68 +FOO!
69 ff
70 ggg
71 hh
72 """)
73
74 PATCH_ADD = ("""\
75 diff --git a/new_dir/subdir/new_file b/new_dir/subdir/new_file
76 new file mode 100644
77 --- /dev/null
78 +++ b/new_dir/subdir/new_file
79 @@ -0,0 +1,2 @@
80 +A new file
81 +should exist.
82 """)
83
84
85 class FakeRepos(fake_repos.FakeReposBase):
86 def populateSvn(self):
87 """Creates a few revisions of changes files."""
88 subprocess2.check_call(
89 ['svn', 'checkout', self.svn_base, self.svn_checkout, '-q',
90 '--non-interactive', '--no-auth-cache',
91 '--username', self.USERS[0][0], '--password', self.USERS[0][1]])
92 assert os.path.isdir(os.path.join(self.svn_checkout, '.svn'))
93 fs = {}
94 fs['trunk/origin'] = 'svn@1'
95 fs['trunk/codereview.settings'] = (
96 '# Test data\n'
97 'bar: pouet\n')
98 fs['trunk/svn_utils_test.txt'] = (
99 'a\n'
100 'bb\n'
101 'ccc\n'
102 'dd\n'
103 'e\n'
104 'ff\n'
105 'ggg\n'
106 'hh\n'
107 'i\n'
108 'jj\n'
109 'kkk\n'
110 'll\n'
111 'm\n'
112 'nn\n'
113 'ooo\n'
114 'pp\n'
115 'q\n')
116 self._commit_svn(fs)
117 fs['trunk/origin'] = 'svn@2\n'
118 fs['trunk/extra'] = 'dummy\n'
119 fs['trunk/bin_file'] = '\x00'
120 self._commit_svn(fs)
121
122 def populateGit(self):
123 raise NotImplementedError()
124
125
126 # pylint: disable=R0201
127 class BaseTest(fake_repos.FakeReposTestBase):
128 name = 'foo'
129 FAKE_REPOS_CLASS = FakeRepos
130
131 def setUp(self):
132 # Need to enforce subversion_config first.
133 checkout.SvnMixIn.svn_config_dir = os.path.join(
134 ROOT_DIR, 'subversion_config')
135 super(BaseTest, self).setUp()
136 self._old_call = subprocess2.call
137 def redirect_call(args, **kwargs):
138 if not DEBUGGING:
139 kwargs.setdefault('stdout', subprocess2.PIPE)
140 kwargs.setdefault('stderr', subprocess2.STDOUT)
141 return self._old_call(args, **kwargs)
142 subprocess2.call = redirect_call
143 self.usr, self.pwd = self.FAKE_REPOS.USERS[0]
144 self.previous_log = None
145
146 def tearDown(self):
147 subprocess2.call = self._old_call
148 super(BaseTest, self).tearDown()
149
150 def get_patches(self):
151 return patch.PatchSet([
152 patch.FilePatchDiff(
153 'svn_utils_test.txt', GIT_PATCH, []),
154 patch.FilePatchBinary('bin_file', '\x00', []),
155 patch.FilePatchDelete('extra', False),
156 patch.FilePatchDiff('new_dir/subdir/new_file', PATCH_ADD, []),
157 ])
158
159 def get_trunk(self, modified):
160 tree = {}
161 subroot = 'trunk/'
162 for k, v in self.FAKE_REPOS.svn_revs[-1].iteritems():
163 if k.startswith(subroot):
164 f = k[len(subroot):]
165 assert f not in tree
166 tree[f] = v
167
168 if modified:
169 content_lines = tree['svn_utils_test.txt'].splitlines(True)
170 tree['svn_utils_test.txt'] = ''.join(
171 content_lines[0:5] + ['FOO!\n'] + content_lines[5:])
172 del tree['extra']
173 tree['new_dir/subdir/new_file'] = 'A new file\nshould exist.\n'
174 return tree
175
176 def _check_base(self, co, root, git, expected):
177 read_only = isinstance(co, checkout.ReadOnlyCheckout)
178 assert not read_only == bool(expected)
179 if not read_only:
180 self.FAKE_REPOS.svn_dirty = True
181
182 self.assertEquals(root, co.project_path)
183 self.assertEquals(self.previous_log['revision'], co.prepare())
184 self.assertEquals('pouet', co.get_settings('bar'))
185 self.assertTree(self.get_trunk(False), root)
186 patches = self.get_patches()
187 co.apply_patch(patches)
188 self.assertEquals(
189 ['bin_file', 'extra', 'new_dir/subdir/new_file', 'svn_utils_test.txt'],
190 sorted(patches.filenames))
191
192 if git:
193 # Hackish to verify _branches() internal function.
194 # pylint: disable=W0212
195 self.assertEquals(
196 (['master', 'working_branch'], 'working_branch'),
197 co.checkout._branches())
198
199 # Verify that the patch is applied even for read only checkout.
200 self.assertTree(self.get_trunk(True), root)
201 fake_author = self.FAKE_REPOS.USERS[1][0]
202 revision = co.commit('msg', fake_author)
203 # Nothing changed.
204 self.assertTree(self.get_trunk(True), root)
205
206 if read_only:
207 self.assertEquals('FAKE', revision)
208 self.assertEquals(self.previous_log['revision'], co.prepare())
209 # Changes should be reverted now.
210 self.assertTree(self.get_trunk(False), root)
211 expected = self.previous_log
212 else:
213 self.assertEquals(self.previous_log['revision'] + 1, revision)
214 self.assertEquals(self.previous_log['revision'] + 1, co.prepare())
215 self.assertTree(self.get_trunk(True), root)
216 expected = expected.copy()
217 expected['msg'] = 'msg'
218 expected['revision'] = self.previous_log['revision'] + 1
219 expected.setdefault('author', fake_author)
220
221 actual = self._log()
222 self.assertEquals(expected, actual)
223
224 def _check_exception(self, co, err_msg):
225 co.prepare()
226 try:
227 co.apply_patch([patch.FilePatchDiff('svn_utils_test.txt', BAD_PATCH, [])])
228 self.fail()
229 except checkout.PatchApplicationFailed, e:
230 self.assertEquals(e.filename, 'svn_utils_test.txt')
231 self.assertEquals(e.status, err_msg)
232
233 def _log(self):
234 raise NotImplementedError()
235
236
237 class SvnBaseTest(BaseTest):
238 def setUp(self):
239 super(SvnBaseTest, self).setUp()
240 self.enabled = self.FAKE_REPOS.set_up_svn()
241 self.assertTrue(self.enabled)
242 self.svn_trunk = 'trunk'
243 self.svn_url = self.svn_base + self.svn_trunk
244 self.previous_log = self._log()
245
246 def _log(self):
247 # Don't use the local checkout in case of caching incorrency.
248 out = subprocess2.check_output(
249 ['svn', 'log', self.svn_url,
250 '--non-interactive', '--no-auth-cache',
251 '--username', self.usr, '--password', self.pwd,
252 '--with-all-revprops', '--xml',
253 '--limit', '1'])
254 logentry = ElementTree.XML(out).find('logentry')
255 if logentry == None:
256 return {'revision': 0}
257 data = {
258 'revision': int(logentry.attrib['revision']),
259 }
260 def set_item(name):
261 item = logentry.find(name)
262 if item != None:
263 data[name] = item.text
264 set_item('author')
265 set_item('msg')
266 revprops = logentry.find('revprops')
267 if revprops != None:
268 data['revprops'] = []
269 for prop in revprops.getiterator('property'):
270 data['revprops'].append((prop.attrib['name'], prop.text))
271 return data
272
273
274 class SvnCheckout(SvnBaseTest):
275 def _get_co(self, read_only):
276 if read_only:
277 return checkout.ReadOnlyCheckout(
278 checkout.SvnCheckout(
279 self.root_dir, self.name, None, None, self.svn_url))
280 else:
281 return checkout.SvnCheckout(
282 self.root_dir, self.name, self.usr, self.pwd, self.svn_url)
283
284 def _check(self, read_only, expected):
285 root = os.path.join(self.root_dir, self.name)
286 self._check_base(self._get_co(read_only), root, False, expected)
287
288 def testAllRW(self):
289 expected = {
290 'author': self.FAKE_REPOS.USERS[0][0],
291 'revprops': [('realauthor', self.FAKE_REPOS.USERS[1][0])]
292 }
293 self._check(False, expected)
294
295 def testAllRO(self):
296 self._check(True, None)
297
298 def testException(self):
299 self._check_exception(
300 self._get_co(True),
301 'patching file svn_utils_test.txt\n'
302 'Hunk #1 FAILED at 3.\n'
303 '1 out of 1 hunk FAILED -- saving rejects to file '
304 'svn_utils_test.txt.rej\n')
305
306 def testSvnProps(self):
307 co = self._get_co(False)
308 co.prepare()
309 try:
310 # svn:ignore can only be applied to directories.
311 svn_props = [('svn:ignore', 'foo')]
312 co.apply_patch(
313 [patch.FilePatchDiff('svn_utils_test.txt', NAKED_PATCH, svn_props)])
314 self.fail()
315 except checkout.PatchApplicationFailed, e:
316 self.assertEquals(e.filename, 'svn_utils_test.txt')
317 self.assertEquals(
318 e.status,
319 "patching file svn_utils_test.txt\n"
320 "svn: Cannot set 'svn:ignore' on a file ('svn_utils_test.txt')\n")
321 co.prepare()
322 svn_props = [('svn:eol-style', 'LF'), ('foo', 'bar')]
323 co.apply_patch(
324 [patch.FilePatchDiff('svn_utils_test.txt', NAKED_PATCH, svn_props)])
325 filepath = os.path.join(self.root_dir, self.name, 'svn_utils_test.txt')
326 # Manually verify the properties.
327 props = subprocess2.check_output(
328 ['svn', 'proplist', filepath],
329 cwd=self.root_dir).splitlines()[1:]
330 props = sorted(p.strip() for p in props)
331 expected_props = dict(svn_props)
332 self.assertEquals(sorted(expected_props.iterkeys()), props)
333 for k, v in expected_props.iteritems():
334 value = subprocess2.check_output(
335 ['svn', 'propget', '--strict', k, filepath],
336 cwd=self.root_dir).strip()
337 self.assertEquals(v, value)
338
339 def testWithRevPropsSupport(self):
340 # Add the hook that will commit in a way that removes the race condition.
341 hook = os.path.join(self.FAKE_REPOS.svn_repo, 'hooks', 'pre-commit')
342 shutil.copyfile(os.path.join(ROOT_DIR, 'sample_pre_commit_hook'), hook)
343 os.chmod(hook, 0755)
344 expected = {
345 'revprops': [('commit-bot', 'user1@example.com')],
346 }
347 self._check(False, expected)
348
349 def testWithRevPropsSupportNotCommitBot(self):
350 # Add the hook that will commit in a way that removes the race condition.
351 hook = os.path.join(self.FAKE_REPOS.svn_repo, 'hooks', 'pre-commit')
352 shutil.copyfile(os.path.join(ROOT_DIR, 'sample_pre_commit_hook'), hook)
353 os.chmod(hook, 0755)
354 co = checkout.SvnCheckout(
355 self.root_dir, self.name,
356 self.FAKE_REPOS.USERS[1][0], self.FAKE_REPOS.USERS[1][1],
357 self.svn_url)
358 root = os.path.join(self.root_dir, self.name)
359 expected = {
360 'author': self.FAKE_REPOS.USERS[1][0],
361 }
362 self._check_base(co, root, False, expected)
363
364 def testAutoProps(self):
365 co = self._get_co(False)
366 co.svn_config = checkout.SvnConfig(
367 os.path.join(ROOT_DIR, 'subversion_config'))
368 co.prepare()
369 patches = self.get_patches()
370 co.apply_patch(patches)
371 self.assertEquals(
372 ['bin_file', 'extra', 'new_dir/subdir/new_file', 'svn_utils_test.txt'],
373 sorted(patches.filenames))
374 # *.txt = svn:eol-style=LF in subversion_config/config.
375 out = subprocess2.check_output(
376 ['svn', 'pget', 'svn:eol-style', 'svn_utils_test.txt'],
377 cwd=co.project_path)
378 self.assertEquals('LF\n', out)
379
380
381 class GitSvnCheckout(SvnBaseTest):
382 name = 'foo.git'
383
384 def _get_co(self, read_only):
385 co = checkout.GitSvnCheckout(
386 self.root_dir, self.name[:-4],
387 self.usr, self.pwd,
388 self.svn_base, self.svn_trunk)
389 if read_only:
390 co = checkout.ReadOnlyCheckout(co)
391 else:
392 # Hack to simplify testing.
393 co.checkout = co
394 return co
395
396 def _check(self, read_only, expected):
397 root = os.path.join(self.root_dir, self.name)
398 self._check_base(self._get_co(read_only), root, True, expected)
399
400 def testAllRO(self):
401 self._check(True, None)
402
403 def testAllRW(self):
404 expected = {
405 'author': self.FAKE_REPOS.USERS[0][0],
406 }
407 self._check(False, expected)
408
409 def testGitSvnPremade(self):
410 # Test premade git-svn clone. First make a git-svn clone.
411 git_svn_co = self._get_co(True)
412 revision = git_svn_co.prepare()
413 self.assertEquals(self.previous_log['revision'], revision)
414 # Then use GitSvnClone to clone it to lose the git-svn connection and verify
415 # git svn init / git svn fetch works.
416 git_svn_clone = checkout.GitSvnPremadeCheckout(
417 self.root_dir, self.name[:-4] + '2', 'trunk',
418 self.usr, self.pwd,
419 self.svn_base, self.svn_trunk, git_svn_co.project_path)
420 self.assertEquals(self.previous_log['revision'], git_svn_clone.prepare())
421
422 def testException(self):
423 self._check_exception(
424 self._get_co(True), 'fatal: corrupt patch at line 12\n')
425
426 def testSvnProps(self):
427 co = self._get_co(False)
428 co.prepare()
429 try:
430 svn_props = [('foo', 'bar')]
431 co.apply_patch(
432 [patch.FilePatchDiff('svn_utils_test.txt', NAKED_PATCH, svn_props)])
433 self.fail()
434 except patch.UnsupportedPatchFormat, e:
435 self.assertEquals(e.filename, 'svn_utils_test.txt')
436 self.assertEquals(
437 e.status,
438 'Cannot apply svn property foo to file svn_utils_test.txt.')
439 co.prepare()
440 # svn:eol-style is ignored.
441 svn_props = [('svn:eol-style', 'LF')]
442 co.apply_patch(
443 [patch.FilePatchDiff('svn_utils_test.txt', NAKED_PATCH, svn_props)])
444
445
446 class RawCheckout(SvnBaseTest):
447 def setUp(self):
448 super(RawCheckout, self).setUp()
449 # Use a svn checkout as the base.
450 self.base_co = checkout.SvnCheckout(
451 self.root_dir, self.name, None, None, self.svn_url)
452 self.base_co.prepare()
453
454 def _get_co(self, read_only):
455 co = checkout.RawCheckout(self.root_dir, self.name)
456 if read_only:
457 return checkout.ReadOnlyCheckout(co)
458 return co
459
460 def _check(self, read_only):
461 root = os.path.join(self.root_dir, self.name)
462 co = self._get_co(read_only)
463
464 # A copy of BaseTest._check_base()
465 self.assertEquals(root, co.project_path)
466 self.assertEquals(None, co.prepare())
467 self.assertEquals('pouet', co.get_settings('bar'))
468 self.assertTree(self.get_trunk(False), root)
469 patches = self.get_patches()
470 co.apply_patch(patches)
471 self.assertEquals(
472 ['bin_file', 'extra', 'new_dir/subdir/new_file', 'svn_utils_test.txt'],
473 sorted(patches.filenames))
474
475 # Verify that the patch is applied even for read only checkout.
476 self.assertTree(self.get_trunk(True), root)
477 if read_only:
478 revision = co.commit('msg', self.FAKE_REPOS.USERS[1][0])
479 self.assertEquals('FAKE', revision)
480 else:
481 try:
482 co.commit('msg', self.FAKE_REPOS.USERS[1][0])
483 self.fail()
484 except NotImplementedError:
485 pass
486 self.assertTree(self.get_trunk(True), root)
487 # Verify that prepare() is a no-op.
488 self.assertEquals(None, co.prepare())
489 self.assertTree(self.get_trunk(True), root)
490
491 def testAllRW(self):
492 self._check(False)
493
494 def testAllRO(self):
495 self._check(True)
496
497 def testException(self):
498 self._check_exception(
499 self._get_co(True),
500 'patching file svn_utils_test.txt\n'
501 'Hunk #1 FAILED at 3.\n'
502 '1 out of 1 hunk FAILED -- saving rejects to file '
503 'svn_utils_test.txt.rej\n')
504
505
506 if __name__ == '__main__':
507 if '-v' in sys.argv:
508 DEBUGGING = True
509 logging.basicConfig(
510 level=logging.DEBUG,
511 format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
512 else:
513 logging.basicConfig(
514 level=logging.ERROR,
515 format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
516 unittest.main()
OLDNEW
« no previous file with comments | « checkout.py ('k') | tests/sample_pre_commit_hook » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698