2009-11-25 Zoltan Horvath <zoltan@webkit.org>
[WebKit-https.git] / WebKitTools / Scripts / modules / scm_unittest.py
1 # Copyright (C) 2009 Google Inc. All rights reserved.
2 # Copyright (C) 2009 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #    * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #    * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #    * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import base64
31 import os
32 import os.path
33 import re
34 import stat
35 import subprocess
36 import tempfile
37 import unittest
38 import urllib
39
40 from datetime import date
41 from modules.scm import detect_scm_system, SCM, ScriptError, CheckoutNeedsUpdate, ignore_error, commit_error_handler
42
43
44 # Eventually we will want to write tests which work for both scms. (like update_webkit, changed_files, etc.)
45 # Perhaps through some SCMTest base-class which both SVNTest and GitTest inherit from.
46
47 def run(args, cwd=None):
48     return SCM.run_command(args, cwd=cwd)
49
50 def run_silent(args, cwd=None):
51     process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
52     process.communicate() # ignore output
53     exit_code = process.wait()
54     if exit_code:
55         raise ScriptError('Failed to run "%s"  exit_code: %d  cwd: %s' % (args, exit_code, cwd))
56
57 def write_into_file_at_path(file_path, contents):
58     file = open(file_path, 'w')
59     file.write(contents)
60     file.close()
61
62 def read_from_path(file_path):
63     file = open(file_path, 'r')
64     contents = file.read()
65     file.close()
66     return contents
67
68 # Exists to share svn repository creation code between the git and svn tests
69 class SVNTestRepository:
70     @staticmethod
71     def _setup_test_commits(test_object):
72         # Add some test commits
73         os.chdir(test_object.svn_checkout_path)
74         test_file = open('test_file', 'w')
75         test_file.write("test1")
76         test_file.flush()
77         
78         run(['svn', 'add', 'test_file'])
79         run(['svn', 'commit', '--quiet', '--message', 'initial commit'])
80         
81         test_file.write("test2")
82         test_file.flush()
83         
84         run(['svn', 'commit', '--quiet', '--message', 'second commit'])
85         
86         test_file.write("test3\n")
87         test_file.flush()
88         
89         run(['svn', 'commit', '--quiet', '--message', 'third commit'])
90
91         test_file.write("test4\n")
92         test_file.close()
93
94         run(['svn', 'commit', '--quiet', '--message', 'fourth commit'])
95
96         # svn does not seem to update after commit as I would expect.
97         run(['svn', 'update'])
98
99     @classmethod
100     def setup(cls, test_object):
101         # Create an test SVN repository
102         test_object.svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo")
103         test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path # Not sure this will work on windows
104         # git svn complains if we don't pass --pre-1.5-compatible, not sure why:
105         # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477
106         run(['svnadmin', 'create', '--pre-1.5-compatible', test_object.svn_repo_path])
107
108         # Create a test svn checkout
109         test_object.svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout")
110         run(['svn', 'checkout', '--quiet', test_object.svn_repo_url, test_object.svn_checkout_path])
111
112         cls._setup_test_commits(test_object)
113
114     @classmethod
115     def tear_down(cls, test_object):
116         run(['rm', '-rf', test_object.svn_repo_path])
117         run(['rm', '-rf', test_object.svn_checkout_path])
118
119 # For testing the SCM baseclass directly.
120 class SCMClassTests(unittest.TestCase):
121     def setUp(self):
122         self.dev_null = open(os.devnull, "w") # Used to make our Popen calls quiet.
123
124     def tearDown(self):
125         self.dev_null.close()
126
127     def test_run_command_with_pipe(self):
128         input_process = subprocess.Popen(['/bin/echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null)
129         self.assertEqual(SCM.run_command(['/usr/bin/grep', 'bar'], input=input_process.stdout), "bar\n")
130
131         # Test the non-pipe case too:
132         self.assertEqual(SCM.run_command(['/usr/bin/grep', 'bar'], input="foo\nbar"), "bar\n")
133
134         command_returns_non_zero = ['/bin/sh', '--invalid-option']
135         # Test when the input pipe process fails.
136         input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null)
137         self.assertTrue(input_process.poll() != 0)
138         self.assertRaises(ScriptError, SCM.run_command, ['/usr/bin/grep', 'bar'], input=input_process.stdout)
139
140         # Test when the run_command process fails.
141         input_process = subprocess.Popen(['/bin/echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) # grep shows usage and calls exit(2) when called w/o arguments.
142         self.assertRaises(ScriptError, SCM.run_command, command_returns_non_zero, input=input_process.stdout)
143
144     def test_error_handlers(self):
145         git_failure_message="Merge conflict during commit: Your file or directory 'WebCore/ChangeLog' is probably out-of-date: resource out of date; try updating at /usr/local/libexec/git-core//git-svn line 469"
146         svn_failure_message="""svn: Commit failed (details follow):
147 svn: File or directory 'ChangeLog' is out of date; try updating
148 svn: resource out of date; try updating
149 """
150         command_does_not_exist = ['does_not_exist', 'invalid_option']
151         self.assertRaises(OSError, SCM.run_command, command_does_not_exist)
152         self.assertRaises(OSError, SCM.run_command, command_does_not_exist, error_handler=ignore_error)
153
154         command_returns_non_zero = ['/bin/sh', '--invalid-option']
155         self.assertRaises(ScriptError, SCM.run_command, command_returns_non_zero)
156         # Check if returns error text:
157         self.assertTrue(SCM.run_command(command_returns_non_zero, error_handler=ignore_error))
158
159         self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=git_failure_message))
160         self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=svn_failure_message))
161         self.assertRaises(ScriptError, commit_error_handler, ScriptError(output='blah blah blah'))
162
163
164 # GitTest and SVNTest inherit from this so any test_ methods here will be run once for this class and then once for each subclass.
165 class SCMTest(unittest.TestCase):
166     def _create_patch(self, patch_contents):
167         patch_path = os.path.join(self.svn_checkout_path, 'patch.diff')
168         write_into_file_at_path(patch_path, patch_contents)
169         patch = {}
170         patch['reviewer'] = 'Joe Cool'
171         patch['bug_id'] = '12345'
172         patch['url'] = 'file://%s' % urllib.pathname2url(patch_path)
173         return patch
174
175     def _setup_webkittools_scripts_symlink(self, local_scm):
176         webkit_scm = detect_scm_system(os.path.dirname(os.path.abspath(__file__)))
177         webkit_scripts_directory = webkit_scm.scripts_directory()
178         local_scripts_directory = local_scm.scripts_directory()
179         os.mkdir(os.path.dirname(local_scripts_directory))
180         os.symlink(webkit_scripts_directory, local_scripts_directory)
181
182     # Tests which both GitTest and SVNTest should run.
183     # FIXME: There must be a simpler way to add these w/o adding a wrapper method to both subclasses
184     def _shared_test_commit_with_message(self):
185         write_into_file_at_path('test_file', 'more test content')
186         commit_text = self.scm.commit_with_message('another test commit')
187         self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '5')
188
189         self.scm.dryrun = True
190         write_into_file_at_path('test_file', 'still more test content')
191         commit_text = self.scm.commit_with_message('yet another test commit')
192         self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0')
193
194     def _shared_test_reverse_diff(self):
195         self._setup_webkittools_scripts_symlink(self.scm) # Git's apply_reverse_diff uses resolve-ChangeLogs
196         # Only test the simple case, as any other will end up with conflict markers.
197         self.scm.apply_reverse_diff('4')
198         self.assertEqual(read_from_path('test_file'), "test1test2test3\n")
199
200     def _shared_test_diff_for_revision(self):
201         # Patch formats are slightly different between svn and git, so just regexp for things we know should be there.
202         r3_patch = self.scm.diff_for_revision(3)
203         self.assertTrue(re.search('test3', r3_patch))
204         self.assertFalse(re.search('test4', r3_patch))
205         self.assertTrue(re.search('test2', r3_patch))
206         self.assertTrue(re.search('test2', self.scm.diff_for_revision(2)))
207
208     def _shared_test_svn_apply_git_patch(self):
209         self._setup_webkittools_scripts_symlink(self.scm)
210         git_binary_addition = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif
211 new file mode 100644
212 index 0000000000000000000000000000000000000000..64a9532e7794fcd791f6f12157406d90
213 60151690
214 GIT binary patch
215 literal 512
216 zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c?
217 zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap
218 zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ
219 zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A
220 zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&)
221 zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b
222 zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB
223 z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X
224 z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4
225 ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H
226
227 literal 0
228 HcmV?d00001
229
230 """
231         self.scm.apply_patch(self._create_patch(git_binary_addition))
232         added = read_from_path('fizzbuzz7.gif')
233         self.assertEqual(512, len(added))
234         self.assertTrue(added.startswith('GIF89a'))
235         self.assertTrue('fizzbuzz7.gif' in self.scm.changed_files())
236
237         # The file already exists.
238         self.assertRaises(ScriptError, self.scm.apply_patch, self._create_patch(git_binary_addition))
239
240         git_binary_modification = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif
241 index 64a9532e7794fcd791f6f12157406d9060151690..323fae03f4606ea9991df8befbb2fca7
242 GIT binary patch
243 literal 7
244 OcmYex&reD$;sO8*F9L)B
245
246 literal 512
247 zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c?
248 zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap
249 zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ
250 zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A
251 zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&)
252 zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b
253 zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB
254 z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X
255 z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4
256 ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H
257
258 """
259         self.scm.apply_patch(self._create_patch(git_binary_modification))
260         modified = read_from_path('fizzbuzz7.gif')
261         self.assertEqual('foobar\n', modified)
262         self.assertTrue('fizzbuzz7.gif' in self.scm.changed_files())
263
264         # Applying the same modification should fail.
265         self.assertRaises(ScriptError, self.scm.apply_patch, self._create_patch(git_binary_modification))
266
267         git_binary_deletion = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif
268 deleted file mode 100644
269 index 323fae0..0000000
270 GIT binary patch
271 literal 0
272 HcmV?d00001
273
274 literal 7
275 OcmYex&reD$;sO8*F9L)B
276
277 """
278         self.scm.apply_patch(self._create_patch(git_binary_deletion))
279         self.assertFalse(os.path.exists('fizzbuzz7.gif'))
280         self.assertFalse('fizzbuzz7.gif' in self.scm.changed_files())
281
282         # Cannot delete again.
283         self.assertRaises(ScriptError, self.scm.apply_patch, self._create_patch(git_binary_deletion))
284
285
286 class SVNTest(SCMTest):
287
288     @staticmethod
289     def _set_date_and_reviewer(changelog_entry):
290         # Joe Cool matches the reviewer set in SCMTest._create_patch
291         changelog_entry = changelog_entry.replace('REVIEWER_HERE', 'Joe Cool')
292         # svn-apply will update ChangeLog entries with today's date.
293         return changelog_entry.replace('DATE_HERE', date.today().isoformat())
294
295     def test_svn_apply(self):
296         first_entry = """2009-10-26  Eric Seidel  <eric@webkit.org>
297
298         Reviewed by Foo Bar.
299
300         Most awesome change ever.
301
302         * scm_unittest.py:
303 """
304         intermediate_entry = """2009-10-27  Eric Seidel  <eric@webkit.org>
305
306         Reviewed by Baz Bar.
307
308         A more awesomer change yet!
309
310         * scm_unittest.py:
311 """
312         one_line_overlap_patch = """Index: ChangeLog
313 ===================================================================
314 --- ChangeLog   (revision 5)
315 +++ ChangeLog   (working copy)
316 @@ -1,5 +1,13 @@
317  2009-10-26  Eric Seidel  <eric@webkit.org>
318
319 +        Reviewed by NOBODY (OOPS!).
320 +
321 +        Second most awsome change ever.
322 +
323 +        * scm_unittest.py:
324 +
325 +2009-10-26  Eric Seidel  <eric@webkit.org>
326 +
327          Reviewed by Foo Bar.
328
329          Most awesome change ever.
330 """
331         one_line_overlap_entry = """DATE_HERE  Eric Seidel  <eric@webkit.org>
332
333         Reviewed by REVIEWER_HERE.
334
335         Second most awsome change ever.
336
337         * scm_unittest.py:
338 """
339         two_line_overlap_patch = """Index: ChangeLog
340 ===================================================================
341 --- ChangeLog   (revision 5)
342 +++ ChangeLog   (working copy)
343 @@ -2,6 +2,14 @@
344
345          Reviewed by Foo Bar.
346
347 +        Second most awsome change ever.
348 +
349 +        * scm_unittest.py:
350 +
351 +2009-10-26  Eric Seidel  <eric@webkit.org>
352 +
353 +        Reviewed by Foo Bar.
354 +
355          Most awesome change ever.
356
357          * scm_unittest.py:
358 """
359         two_line_overlap_entry = """DATE_HERE  Eric Seidel  <eric@webkit.org>
360
361         Reviewed by Foo Bar.
362
363         Second most awsome change ever.
364
365         * scm_unittest.py:
366 """
367         write_into_file_at_path('ChangeLog', first_entry)
368         run(['svn', 'add', 'ChangeLog'])
369         run(['svn', 'commit', '--quiet', '--message', 'ChangeLog commit'])
370
371         # Patch files were created against just 'first_entry'.
372         # Add a second commit to make svn-apply have to apply the patches with fuzz.
373         changelog_contents = "%s\n%s" % (intermediate_entry, first_entry)
374         write_into_file_at_path('ChangeLog', changelog_contents)
375         run(['svn', 'commit', '--quiet', '--message', 'Intermediate commit'])
376
377         self._setup_webkittools_scripts_symlink(self.scm)
378         self.scm.apply_patch(self._create_patch(one_line_overlap_patch))
379         expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(one_line_overlap_entry), changelog_contents)
380         self.assertEquals(read_from_path('ChangeLog'), expected_changelog_contents)
381
382         self.scm.revert_files(['ChangeLog'])
383         self.scm.apply_patch(self._create_patch(two_line_overlap_patch))
384         expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(two_line_overlap_entry), changelog_contents)
385         self.assertEquals(read_from_path('ChangeLog'), expected_changelog_contents)
386
387     def setUp(self):
388         SVNTestRepository.setup(self)
389         os.chdir(self.svn_checkout_path)
390         self.scm = detect_scm_system(self.svn_checkout_path)
391
392     def tearDown(self):
393         SVNTestRepository.tear_down(self)
394
395     def test_create_patch_is_full_patch(self):
396         test_dir_path = os.path.join(self.svn_checkout_path, 'test_dir')
397         os.mkdir(test_dir_path)
398         test_file_path = os.path.join(test_dir_path, 'test_file2')
399         write_into_file_at_path(test_file_path, 'test content')
400         run(['svn', 'add', 'test_dir'])
401
402         # create_patch depends on 'svn-create-patch', so make a dummy version.
403         scripts_path = os.path.join(self.svn_checkout_path, 'WebKitTools', 'Scripts')
404         os.makedirs(scripts_path)
405         create_patch_path = os.path.join(scripts_path, 'svn-create-patch')
406         write_into_file_at_path(create_patch_path, '#!/bin/sh\necho $PWD')
407         os.chmod(create_patch_path, stat.S_IXUSR | stat.S_IRUSR)
408
409         # Change into our test directory and run the create_patch command.
410         os.chdir(test_dir_path)
411         scm = detect_scm_system(test_dir_path)
412         self.assertEqual(scm.checkout_root, self.svn_checkout_path) # Sanity check that detection worked right.
413         patch_contents = scm.create_patch()
414         # Our fake 'svn-create-patch' returns $PWD instead of a patch, check that it was executed from the root of the repo.
415         self.assertEqual(os.path.realpath(scm.checkout_root), patch_contents)
416
417     def test_detection(self):
418         scm = detect_scm_system(self.svn_checkout_path)
419         self.assertEqual(scm.display_name(), "svn")
420         self.assertEqual(scm.supports_local_commits(), False)
421
422     def test_apply_small_binary_patch(self):
423         patch_contents = """Index: test_file.swf
424 ===================================================================
425 Cannot display: file marked as a binary type.
426 svn:mime-type = application/octet-stream
427
428 Property changes on: test_file.swf
429 ___________________________________________________________________
430 Name: svn:mime-type
431    + application/octet-stream
432
433
434 Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
435 """
436         expected_contents = base64.b64decode("Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==")
437         self._setup_webkittools_scripts_symlink(self.scm)
438         patch_file = self._create_patch(patch_contents)
439         self.scm.apply_patch(patch_file)
440         actual_contents = read_from_path("test_file.swf")
441         self.assertEqual(actual_contents, expected_contents)
442
443     def test_apply_svn_patch(self):
444         scm = detect_scm_system(self.svn_checkout_path)
445         patch = self._create_patch(run(['svn', 'diff', '-r4:3']))
446         self._setup_webkittools_scripts_symlink(scm)
447         scm.apply_patch(patch)
448
449     def test_apply_svn_patch_force(self):
450         scm = detect_scm_system(self.svn_checkout_path)
451         patch = self._create_patch(run(['svn', 'diff', '-r2:4']))
452         self._setup_webkittools_scripts_symlink(scm)
453         self.assertRaises(ScriptError, scm.apply_patch, patch, force=True)
454
455     def test_commit_logs(self):
456         # Commits have dates and usernames in them, so we can't just direct compare.
457         self.assertTrue(re.search('fourth commit', self.scm.last_svn_commit_log()))
458         self.assertTrue(re.search('second commit', self.scm.svn_commit_log(2)))
459
460     def test_commit_text_parsing(self):
461         self._shared_test_commit_with_message()
462
463     def test_reverse_diff(self):
464         self._shared_test_reverse_diff()
465
466     def test_diff_for_revision(self):
467         self._shared_test_diff_for_revision()
468
469     def test_svn_apply_git_patch(self):
470         self._shared_test_svn_apply_git_patch()
471
472 class GitTest(SCMTest):
473
474     def _setup_git_clone_of_svn_repository(self):
475         self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout")
476         # --quiet doesn't make git svn silent, so we use run_silent to redirect output
477         run_silent(['git', 'svn', '--quiet', 'clone', self.svn_repo_url, self.git_checkout_path])
478
479     def _tear_down_git_clone_of_svn_repository(self):
480         run(['rm', '-rf', self.git_checkout_path])
481
482     def setUp(self):
483         SVNTestRepository.setup(self)
484         self._setup_git_clone_of_svn_repository()
485         os.chdir(self.git_checkout_path)
486         self.scm = detect_scm_system(self.git_checkout_path)
487
488     def tearDown(self):
489         SVNTestRepository.tear_down(self)
490         self._tear_down_git_clone_of_svn_repository()
491
492     def test_detection(self):
493         scm = detect_scm_system(self.git_checkout_path)
494         self.assertEqual(scm.display_name(), "git")
495         self.assertEqual(scm.supports_local_commits(), True)
496
497     def test_rebase_in_progress(self):
498         svn_test_file = os.path.join(self.svn_checkout_path, 'test_file')
499         write_into_file_at_path(svn_test_file, "svn_checkout")
500         run(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path)
501
502         git_test_file = os.path.join(self.git_checkout_path, 'test_file')
503         write_into_file_at_path(git_test_file, "git_checkout")
504         run(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort'])
505
506         # --quiet doesn't make git svn silent, so use run_silent to redirect output
507         self.assertRaises(ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase']) # Will fail due to a conflict leaving us mid-rebase.
508
509         scm = detect_scm_system(self.git_checkout_path)
510         self.assertTrue(scm.rebase_in_progress())
511
512         # Make sure our cleanup works.
513         scm.clean_working_directory()
514         self.assertFalse(scm.rebase_in_progress())
515
516         # Make sure cleanup doesn't throw when no rebase is in progress.
517         scm.clean_working_directory()
518
519     def test_commitish_parsing(self):
520         scm = detect_scm_system(self.git_checkout_path)
521     
522         # Multiple revisions are cherry-picked.
523         self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1)
524         self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2)
525     
526         # ... is an invalid range specifier
527         self.assertRaises(ScriptError, scm.commit_ids_from_commitish_arguments, ['trunk...HEAD'])
528
529     def test_commitish_order(self):
530         scm = detect_scm_system(self.git_checkout_path)
531
532         commit_range = 'HEAD~3..HEAD'
533
534         actual_commits = scm.commit_ids_from_commitish_arguments([commit_range])
535         expected_commits = []
536         expected_commits += reversed(run(['git', 'rev-list', commit_range]).splitlines())
537
538         self.assertEqual(actual_commits, expected_commits)
539
540     def test_apply_git_patch(self):
541         scm = detect_scm_system(self.git_checkout_path)
542         patch = self._create_patch(run(['git', 'diff', 'HEAD..HEAD^']))
543         self._setup_webkittools_scripts_symlink(scm)
544         scm.apply_patch(patch)
545
546     def test_apply_git_patch_force(self):
547         scm = detect_scm_system(self.git_checkout_path)
548         patch = self._create_patch(run(['git', 'diff', 'HEAD~2..HEAD']))
549         self._setup_webkittools_scripts_symlink(scm)
550         self.assertRaises(ScriptError, scm.apply_patch, patch, force=True)
551
552     def test_commit_text_parsing(self):
553         self._shared_test_commit_with_message()
554
555     def test_reverse_diff(self):
556         self._shared_test_reverse_diff()
557
558     def test_diff_for_revision(self):
559         self._shared_test_diff_for_revision()
560
561     def test_svn_apply_git_patch(self):
562         self._shared_test_svn_apply_git_patch()
563
564     def test_create_binary_patch(self):
565         # Create a git binary patch and check the contents.
566         scm = detect_scm_system(self.git_checkout_path)
567         test_file_name = 'binary_file'
568         test_file_path = os.path.join(self.git_checkout_path, test_file_name)
569         file_contents = ''.join(map(chr, range(256)))
570         write_into_file_at_path(test_file_path, file_contents)
571         run(['git', 'add', test_file_name])
572         patch = scm.create_patch()
573         self.assertTrue(re.search(r'\nliteral 0\n', patch))
574         self.assertTrue(re.search(r'\nliteral 256\n', patch))
575
576         # Check if we can apply the created patch.
577         run(['git', 'rm', '-f', test_file_name])
578         self._setup_webkittools_scripts_symlink(scm)
579         self.scm.apply_patch(self._create_patch(patch))
580         self.assertEqual(file_contents, read_from_path(test_file_path))
581
582         # Check if we can create a patch from a local commit.
583         write_into_file_at_path(test_file_path, file_contents)
584         run(['git', 'add', test_file_name])
585         run(['git', 'commit', '-m', 'binary diff'])
586         patch_from_local_commit = scm.create_patch_from_local_commit('HEAD')
587         self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit))
588         self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit))
589         patch_since_local_commit = scm.create_patch_since_local_commit('HEAD^1')
590         self.assertTrue(re.search(r'\nliteral 0\n', patch_since_local_commit))
591         self.assertTrue(re.search(r'\nliteral 256\n', patch_since_local_commit))
592         self.assertEqual(patch_from_local_commit, patch_since_local_commit)
593
594
595 if __name__ == '__main__':
596     unittest.main()