|
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 from __future__ import with_statement |
|
31 |
|
32 import base64 |
|
33 import codecs |
|
34 import getpass |
|
35 import os |
|
36 import os.path |
|
37 import re |
|
38 import stat |
|
39 import subprocess |
|
40 import tempfile |
|
41 import unittest |
|
42 import urllib |
|
43 import shutil |
|
44 |
|
45 from datetime import date |
|
46 from webkitpy.common.checkout.api import Checkout |
|
47 from webkitpy.common.checkout.scm import detect_scm_system, SCM, SVN, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError, AmbiguousCommitError |
|
48 from webkitpy.common.config.committers import Committer # FIXME: This should not be needed |
|
49 from webkitpy.common.net.bugzilla import Attachment # FIXME: This should not be needed |
|
50 from webkitpy.common.system.executive import Executive, run_command, ScriptError |
|
51 |
|
52 # Eventually we will want to write tests which work for both scms. (like update_webkit, changed_files, etc.) |
|
53 # Perhaps through some SCMTest base-class which both SVNTest and GitTest inherit from. |
|
54 |
|
55 # FIXME: This should be unified into one of the executive.py commands! |
|
56 # Callers could use run_and_throw_if_fail(args, cwd=cwd, quiet=True) |
|
57 def run_silent(args, cwd=None): |
|
58 # Note: Not thread safe: http://bugs.python.org/issue2320 |
|
59 process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) |
|
60 process.communicate() # ignore output |
|
61 exit_code = process.wait() |
|
62 if exit_code: |
|
63 raise ScriptError('Failed to run "%s" exit_code: %d cwd: %s' % (args, exit_code, cwd)) |
|
64 |
|
65 |
|
66 def write_into_file_at_path(file_path, contents, encoding="utf-8"): |
|
67 if encoding: |
|
68 with codecs.open(file_path, "w", encoding) as file: |
|
69 file.write(contents) |
|
70 else: |
|
71 with open(file_path, "w") as file: |
|
72 file.write(contents) |
|
73 |
|
74 |
|
75 def read_from_path(file_path, encoding="utf-8"): |
|
76 with codecs.open(file_path, "r", encoding) as file: |
|
77 return file.read() |
|
78 |
|
79 |
|
80 def _make_diff(command, *args): |
|
81 # We use this wrapper to disable output decoding. diffs should be treated as |
|
82 # binary files since they may include text files of multiple differnet encodings. |
|
83 return run_command([command, "diff"] + list(args), decode_output=False) |
|
84 |
|
85 |
|
86 def _svn_diff(*args): |
|
87 return _make_diff("svn", *args) |
|
88 |
|
89 |
|
90 def _git_diff(*args): |
|
91 return _make_diff("git", *args) |
|
92 |
|
93 |
|
94 # Exists to share svn repository creation code between the git and svn tests |
|
95 class SVNTestRepository: |
|
96 @classmethod |
|
97 def _svn_add(cls, path): |
|
98 run_command(["svn", "add", path]) |
|
99 |
|
100 @classmethod |
|
101 def _svn_commit(cls, message): |
|
102 run_command(["svn", "commit", "--quiet", "--message", message]) |
|
103 |
|
104 @classmethod |
|
105 def _setup_test_commits(cls, test_object): |
|
106 # Add some test commits |
|
107 os.chdir(test_object.svn_checkout_path) |
|
108 |
|
109 write_into_file_at_path("test_file", "test1") |
|
110 cls._svn_add("test_file") |
|
111 cls._svn_commit("initial commit") |
|
112 |
|
113 write_into_file_at_path("test_file", "test1test2") |
|
114 # This used to be the last commit, but doing so broke |
|
115 # GitTest.test_apply_git_patch which use the inverse diff of the last commit. |
|
116 # svn-apply fails to remove directories in Git, see: |
|
117 # https://bugs.webkit.org/show_bug.cgi?id=34871 |
|
118 os.mkdir("test_dir") |
|
119 # Slash should always be the right path separator since we use cygwin on Windows. |
|
120 test_file3_path = "test_dir/test_file3" |
|
121 write_into_file_at_path(test_file3_path, "third file") |
|
122 cls._svn_add("test_dir") |
|
123 cls._svn_commit("second commit") |
|
124 |
|
125 write_into_file_at_path("test_file", "test1test2test3\n") |
|
126 write_into_file_at_path("test_file2", "second file") |
|
127 cls._svn_add("test_file2") |
|
128 cls._svn_commit("third commit") |
|
129 |
|
130 # This 4th commit is used to make sure that our patch file handling |
|
131 # code correctly treats patches as binary and does not attempt to |
|
132 # decode them assuming they're utf-8. |
|
133 write_into_file_at_path("test_file", u"latin1 test: \u00A0\n", "latin1") |
|
134 write_into_file_at_path("test_file2", u"utf-8 test: \u00A0\n", "utf-8") |
|
135 cls._svn_commit("fourth commit") |
|
136 |
|
137 # svn does not seem to update after commit as I would expect. |
|
138 run_command(['svn', 'update']) |
|
139 |
|
140 @classmethod |
|
141 def setup(cls, test_object): |
|
142 # Create an test SVN repository |
|
143 test_object.svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo") |
|
144 test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path # Not sure this will work on windows |
|
145 # git svn complains if we don't pass --pre-1.5-compatible, not sure why: |
|
146 # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477 |
|
147 run_command(['svnadmin', 'create', '--pre-1.5-compatible', test_object.svn_repo_path]) |
|
148 |
|
149 # Create a test svn checkout |
|
150 test_object.svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout") |
|
151 run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url, test_object.svn_checkout_path]) |
|
152 |
|
153 # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations |
|
154 os.chdir(test_object.svn_checkout_path) |
|
155 os.mkdir('trunk') |
|
156 cls._svn_add('trunk') |
|
157 # We can add tags and branches as well if we ever need to test those. |
|
158 cls._svn_commit('add trunk') |
|
159 |
|
160 # Change directory out of the svn checkout so we can delete the checkout directory. |
|
161 # _setup_test_commits will CD back to the svn checkout directory. |
|
162 os.chdir('/') |
|
163 run_command(['rm', '-rf', test_object.svn_checkout_path]) |
|
164 run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url + '/trunk', test_object.svn_checkout_path]) |
|
165 |
|
166 cls._setup_test_commits(test_object) |
|
167 |
|
168 @classmethod |
|
169 def tear_down(cls, test_object): |
|
170 run_command(['rm', '-rf', test_object.svn_repo_path]) |
|
171 run_command(['rm', '-rf', test_object.svn_checkout_path]) |
|
172 |
|
173 # Now that we've deleted the checkout paths, cwddir may be invalid |
|
174 # Change back to a valid directory so that later calls to os.getcwd() do not fail. |
|
175 os.chdir(detect_scm_system(os.path.dirname(__file__)).checkout_root) |
|
176 |
|
177 # For testing the SCM baseclass directly. |
|
178 class SCMClassTests(unittest.TestCase): |
|
179 def setUp(self): |
|
180 self.dev_null = open(os.devnull, "w") # Used to make our Popen calls quiet. |
|
181 |
|
182 def tearDown(self): |
|
183 self.dev_null.close() |
|
184 |
|
185 def test_run_command_with_pipe(self): |
|
186 input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) |
|
187 self.assertEqual(run_command(['grep', 'bar'], input=input_process.stdout), "bar\n") |
|
188 |
|
189 # Test the non-pipe case too: |
|
190 self.assertEqual(run_command(['grep', 'bar'], input="foo\nbar"), "bar\n") |
|
191 |
|
192 command_returns_non_zero = ['/bin/sh', '--invalid-option'] |
|
193 # Test when the input pipe process fails. |
|
194 input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null) |
|
195 self.assertTrue(input_process.poll() != 0) |
|
196 self.assertRaises(ScriptError, run_command, ['grep', 'bar'], input=input_process.stdout) |
|
197 |
|
198 # Test when the run_command process fails. |
|
199 input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) # grep shows usage and calls exit(2) when called w/o arguments. |
|
200 self.assertRaises(ScriptError, run_command, command_returns_non_zero, input=input_process.stdout) |
|
201 |
|
202 def test_error_handlers(self): |
|
203 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" |
|
204 svn_failure_message="""svn: Commit failed (details follow): |
|
205 svn: File or directory 'ChangeLog' is out of date; try updating |
|
206 svn: resource out of date; try updating |
|
207 """ |
|
208 command_does_not_exist = ['does_not_exist', 'invalid_option'] |
|
209 self.assertRaises(OSError, run_command, command_does_not_exist) |
|
210 self.assertRaises(OSError, run_command, command_does_not_exist, error_handler=Executive.ignore_error) |
|
211 |
|
212 command_returns_non_zero = ['/bin/sh', '--invalid-option'] |
|
213 self.assertRaises(ScriptError, run_command, command_returns_non_zero) |
|
214 # Check if returns error text: |
|
215 self.assertTrue(run_command(command_returns_non_zero, error_handler=Executive.ignore_error)) |
|
216 |
|
217 self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=git_failure_message)) |
|
218 self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=svn_failure_message)) |
|
219 self.assertRaises(ScriptError, commit_error_handler, ScriptError(output='blah blah blah')) |
|
220 |
|
221 |
|
222 # GitTest and SVNTest inherit from this so any test_ methods here will be run once for this class and then once for each subclass. |
|
223 class SCMTest(unittest.TestCase): |
|
224 def _create_patch(self, patch_contents): |
|
225 # FIXME: This code is brittle if the Attachment API changes. |
|
226 attachment = Attachment({"bug_id": 12345}, None) |
|
227 attachment.contents = lambda: patch_contents |
|
228 |
|
229 joe_cool = Committer(name="Joe Cool", email_or_emails=None) |
|
230 attachment.reviewer = lambda: joe_cool |
|
231 |
|
232 return attachment |
|
233 |
|
234 def _setup_webkittools_scripts_symlink(self, local_scm): |
|
235 webkit_scm = detect_scm_system(os.path.dirname(os.path.abspath(__file__))) |
|
236 webkit_scripts_directory = webkit_scm.scripts_directory() |
|
237 local_scripts_directory = local_scm.scripts_directory() |
|
238 os.mkdir(os.path.dirname(local_scripts_directory)) |
|
239 os.symlink(webkit_scripts_directory, local_scripts_directory) |
|
240 |
|
241 # Tests which both GitTest and SVNTest should run. |
|
242 # FIXME: There must be a simpler way to add these w/o adding a wrapper method to both subclasses |
|
243 |
|
244 def _shared_test_changed_files(self): |
|
245 write_into_file_at_path("test_file", "changed content") |
|
246 self.assertEqual(self.scm.changed_files(), ["test_file"]) |
|
247 write_into_file_at_path("test_dir/test_file3", "new stuff") |
|
248 self.assertEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"]) |
|
249 old_cwd = os.getcwd() |
|
250 os.chdir("test_dir") |
|
251 # Validate that changed_files does not change with our cwd, see bug 37015. |
|
252 self.assertEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"]) |
|
253 os.chdir(old_cwd) |
|
254 |
|
255 def _shared_test_added_files(self): |
|
256 write_into_file_at_path("test_file", "changed content") |
|
257 self.assertEqual(self.scm.added_files(), []) |
|
258 |
|
259 write_into_file_at_path("added_file", "new stuff") |
|
260 self.scm.add("added_file") |
|
261 |
|
262 os.mkdir("added_dir") |
|
263 write_into_file_at_path("added_dir/added_file2", "new stuff") |
|
264 self.scm.add("added_dir") |
|
265 |
|
266 # SVN reports directory changes, Git does not. |
|
267 added_files = self.scm.added_files() |
|
268 if "added_dir" in added_files: |
|
269 added_files.remove("added_dir") |
|
270 self.assertEqual(added_files, ["added_dir/added_file2", "added_file"]) |
|
271 |
|
272 # Test also to make sure clean_working_directory removes added files |
|
273 self.scm.clean_working_directory() |
|
274 self.assertEqual(self.scm.added_files(), []) |
|
275 self.assertFalse(os.path.exists("added_file")) |
|
276 self.assertFalse(os.path.exists("added_dir")) |
|
277 |
|
278 def _shared_test_changed_files_for_revision(self): |
|
279 # SVN reports directory changes, Git does not. |
|
280 changed_files = self.scm.changed_files_for_revision(3) |
|
281 if "test_dir" in changed_files: |
|
282 changed_files.remove("test_dir") |
|
283 self.assertEqual(changed_files, ["test_dir/test_file3", "test_file"]) |
|
284 self.assertEqual(sorted(self.scm.changed_files_for_revision(4)), sorted(["test_file", "test_file2"])) # Git and SVN return different orders. |
|
285 self.assertEqual(self.scm.changed_files_for_revision(2), ["test_file"]) |
|
286 |
|
287 def _shared_test_contents_at_revision(self): |
|
288 self.assertEqual(self.scm.contents_at_revision("test_file", 3), "test1test2") |
|
289 self.assertEqual(self.scm.contents_at_revision("test_file", 4), "test1test2test3\n") |
|
290 |
|
291 # Verify that contents_at_revision returns a byte array, aka str(): |
|
292 self.assertEqual(self.scm.contents_at_revision("test_file", 5), u"latin1 test: \u00A0\n".encode("latin1")) |
|
293 self.assertEqual(self.scm.contents_at_revision("test_file2", 5), u"utf-8 test: \u00A0\n".encode("utf-8")) |
|
294 |
|
295 self.assertEqual(self.scm.contents_at_revision("test_file2", 4), "second file") |
|
296 # Files which don't exist: |
|
297 # Currently we raise instead of returning None because detecting the difference between |
|
298 # "file not found" and any other error seems impossible with svn (git seems to expose such through the return code). |
|
299 self.assertRaises(ScriptError, self.scm.contents_at_revision, "test_file2", 2) |
|
300 self.assertRaises(ScriptError, self.scm.contents_at_revision, "does_not_exist", 2) |
|
301 |
|
302 def _shared_test_committer_email_for_revision(self): |
|
303 self.assertEqual(self.scm.committer_email_for_revision(3), getpass.getuser()) # Committer "email" will be the current user |
|
304 |
|
305 def _shared_test_reverse_diff(self): |
|
306 self._setup_webkittools_scripts_symlink(self.scm) # Git's apply_reverse_diff uses resolve-ChangeLogs |
|
307 # Only test the simple case, as any other will end up with conflict markers. |
|
308 self.scm.apply_reverse_diff('5') |
|
309 self.assertEqual(read_from_path('test_file'), "test1test2test3\n") |
|
310 |
|
311 def _shared_test_diff_for_revision(self): |
|
312 # Patch formats are slightly different between svn and git, so just regexp for things we know should be there. |
|
313 r3_patch = self.scm.diff_for_revision(4) |
|
314 self.assertTrue(re.search('test3', r3_patch)) |
|
315 self.assertFalse(re.search('test4', r3_patch)) |
|
316 self.assertTrue(re.search('test2', r3_patch)) |
|
317 self.assertTrue(re.search('test2', self.scm.diff_for_revision(3))) |
|
318 |
|
319 def _shared_test_svn_apply_git_patch(self): |
|
320 self._setup_webkittools_scripts_symlink(self.scm) |
|
321 git_binary_addition = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif |
|
322 new file mode 100644 |
|
323 index 0000000000000000000000000000000000000000..64a9532e7794fcd791f6f12157406d90 |
|
324 60151690 |
|
325 GIT binary patch |
|
326 literal 512 |
|
327 zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c? |
|
328 zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap |
|
329 zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ |
|
330 zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A |
|
331 zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&) |
|
332 zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b |
|
333 zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB |
|
334 z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X |
|
335 z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4 |
|
336 ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H |
|
337 |
|
338 literal 0 |
|
339 HcmV?d00001 |
|
340 |
|
341 """ |
|
342 self.checkout.apply_patch(self._create_patch(git_binary_addition)) |
|
343 added = read_from_path('fizzbuzz7.gif', encoding=None) |
|
344 self.assertEqual(512, len(added)) |
|
345 self.assertTrue(added.startswith('GIF89a')) |
|
346 self.assertTrue('fizzbuzz7.gif' in self.scm.changed_files()) |
|
347 |
|
348 # The file already exists. |
|
349 self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_addition)) |
|
350 |
|
351 git_binary_modification = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif |
|
352 index 64a9532e7794fcd791f6f12157406d9060151690..323fae03f4606ea9991df8befbb2fca7 |
|
353 GIT binary patch |
|
354 literal 7 |
|
355 OcmYex&reD$;sO8*F9L)B |
|
356 |
|
357 literal 512 |
|
358 zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c? |
|
359 zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap |
|
360 zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ |
|
361 zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A |
|
362 zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&) |
|
363 zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b |
|
364 zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB |
|
365 z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X |
|
366 z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4 |
|
367 ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H |
|
368 |
|
369 """ |
|
370 self.checkout.apply_patch(self._create_patch(git_binary_modification)) |
|
371 modified = read_from_path('fizzbuzz7.gif', encoding=None) |
|
372 self.assertEqual('foobar\n', modified) |
|
373 self.assertTrue('fizzbuzz7.gif' in self.scm.changed_files()) |
|
374 |
|
375 # Applying the same modification should fail. |
|
376 self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_modification)) |
|
377 |
|
378 git_binary_deletion = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif |
|
379 deleted file mode 100644 |
|
380 index 323fae0..0000000 |
|
381 GIT binary patch |
|
382 literal 0 |
|
383 HcmV?d00001 |
|
384 |
|
385 literal 7 |
|
386 OcmYex&reD$;sO8*F9L)B |
|
387 |
|
388 """ |
|
389 self.checkout.apply_patch(self._create_patch(git_binary_deletion)) |
|
390 self.assertFalse(os.path.exists('fizzbuzz7.gif')) |
|
391 self.assertFalse('fizzbuzz7.gif' in self.scm.changed_files()) |
|
392 |
|
393 # Cannot delete again. |
|
394 self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_deletion)) |
|
395 |
|
396 def _shared_test_add_recursively(self): |
|
397 os.mkdir("added_dir") |
|
398 write_into_file_at_path("added_dir/added_file", "new stuff") |
|
399 self.scm.add("added_dir/added_file") |
|
400 self.assertTrue("added_dir/added_file" in self.scm.added_files()) |
|
401 |
|
402 class SVNTest(SCMTest): |
|
403 |
|
404 @staticmethod |
|
405 def _set_date_and_reviewer(changelog_entry): |
|
406 # Joe Cool matches the reviewer set in SCMTest._create_patch |
|
407 changelog_entry = changelog_entry.replace('REVIEWER_HERE', 'Joe Cool') |
|
408 # svn-apply will update ChangeLog entries with today's date. |
|
409 return changelog_entry.replace('DATE_HERE', date.today().isoformat()) |
|
410 |
|
411 def test_svn_apply(self): |
|
412 first_entry = """2009-10-26 Eric Seidel <eric@webkit.org> |
|
413 |
|
414 Reviewed by Foo Bar. |
|
415 |
|
416 Most awesome change ever. |
|
417 |
|
418 * scm_unittest.py: |
|
419 """ |
|
420 intermediate_entry = """2009-10-27 Eric Seidel <eric@webkit.org> |
|
421 |
|
422 Reviewed by Baz Bar. |
|
423 |
|
424 A more awesomer change yet! |
|
425 |
|
426 * scm_unittest.py: |
|
427 """ |
|
428 one_line_overlap_patch = """Index: ChangeLog |
|
429 =================================================================== |
|
430 --- ChangeLog (revision 5) |
|
431 +++ ChangeLog (working copy) |
|
432 @@ -1,5 +1,13 @@ |
|
433 2009-10-26 Eric Seidel <eric@webkit.org> |
|
434 |
|
435 + Reviewed by NOBODY (OOPS!). |
|
436 + |
|
437 + Second most awesome change ever. |
|
438 + |
|
439 + * scm_unittest.py: |
|
440 + |
|
441 +2009-10-26 Eric Seidel <eric@webkit.org> |
|
442 + |
|
443 Reviewed by Foo Bar. |
|
444 |
|
445 Most awesome change ever. |
|
446 """ |
|
447 one_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org> |
|
448 |
|
449 Reviewed by REVIEWER_HERE. |
|
450 |
|
451 Second most awesome change ever. |
|
452 |
|
453 * scm_unittest.py: |
|
454 """ |
|
455 two_line_overlap_patch = """Index: ChangeLog |
|
456 =================================================================== |
|
457 --- ChangeLog (revision 5) |
|
458 +++ ChangeLog (working copy) |
|
459 @@ -2,6 +2,14 @@ |
|
460 |
|
461 Reviewed by Foo Bar. |
|
462 |
|
463 + Second most awesome change ever. |
|
464 + |
|
465 + * scm_unittest.py: |
|
466 + |
|
467 +2009-10-26 Eric Seidel <eric@webkit.org> |
|
468 + |
|
469 + Reviewed by Foo Bar. |
|
470 + |
|
471 Most awesome change ever. |
|
472 |
|
473 * scm_unittest.py: |
|
474 """ |
|
475 two_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org> |
|
476 |
|
477 Reviewed by Foo Bar. |
|
478 |
|
479 Second most awesome change ever. |
|
480 |
|
481 * scm_unittest.py: |
|
482 """ |
|
483 write_into_file_at_path('ChangeLog', first_entry) |
|
484 run_command(['svn', 'add', 'ChangeLog']) |
|
485 run_command(['svn', 'commit', '--quiet', '--message', 'ChangeLog commit']) |
|
486 |
|
487 # Patch files were created against just 'first_entry'. |
|
488 # Add a second commit to make svn-apply have to apply the patches with fuzz. |
|
489 changelog_contents = "%s\n%s" % (intermediate_entry, first_entry) |
|
490 write_into_file_at_path('ChangeLog', changelog_contents) |
|
491 run_command(['svn', 'commit', '--quiet', '--message', 'Intermediate commit']) |
|
492 |
|
493 self._setup_webkittools_scripts_symlink(self.scm) |
|
494 self.checkout.apply_patch(self._create_patch(one_line_overlap_patch)) |
|
495 expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(one_line_overlap_entry), changelog_contents) |
|
496 self.assertEquals(read_from_path('ChangeLog'), expected_changelog_contents) |
|
497 |
|
498 self.scm.revert_files(['ChangeLog']) |
|
499 self.checkout.apply_patch(self._create_patch(two_line_overlap_patch)) |
|
500 expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(two_line_overlap_entry), changelog_contents) |
|
501 self.assertEquals(read_from_path('ChangeLog'), expected_changelog_contents) |
|
502 |
|
503 def setUp(self): |
|
504 SVNTestRepository.setup(self) |
|
505 os.chdir(self.svn_checkout_path) |
|
506 self.scm = detect_scm_system(self.svn_checkout_path) |
|
507 # For historical reasons, we test some checkout code here too. |
|
508 self.checkout = Checkout(self.scm) |
|
509 |
|
510 def tearDown(self): |
|
511 SVNTestRepository.tear_down(self) |
|
512 |
|
513 def test_detect_scm_system_relative_url(self): |
|
514 scm = detect_scm_system(".") |
|
515 # I wanted to assert that we got the right path, but there was some |
|
516 # crazy magic with temp folder names that I couldn't figure out. |
|
517 self.assertTrue(scm.checkout_root) |
|
518 |
|
519 def test_create_patch_is_full_patch(self): |
|
520 test_dir_path = os.path.join(self.svn_checkout_path, "test_dir2") |
|
521 os.mkdir(test_dir_path) |
|
522 test_file_path = os.path.join(test_dir_path, 'test_file2') |
|
523 write_into_file_at_path(test_file_path, 'test content') |
|
524 run_command(['svn', 'add', 'test_dir2']) |
|
525 |
|
526 # create_patch depends on 'svn-create-patch', so make a dummy version. |
|
527 scripts_path = os.path.join(self.svn_checkout_path, 'WebKitTools', 'Scripts') |
|
528 os.makedirs(scripts_path) |
|
529 create_patch_path = os.path.join(scripts_path, 'svn-create-patch') |
|
530 write_into_file_at_path(create_patch_path, '#!/bin/sh\necho $PWD') # We could pass -n to prevent the \n, but not all echo accept -n. |
|
531 os.chmod(create_patch_path, stat.S_IXUSR | stat.S_IRUSR) |
|
532 |
|
533 # Change into our test directory and run the create_patch command. |
|
534 os.chdir(test_dir_path) |
|
535 scm = detect_scm_system(test_dir_path) |
|
536 self.assertEqual(scm.checkout_root, self.svn_checkout_path) # Sanity check that detection worked right. |
|
537 patch_contents = scm.create_patch() |
|
538 # Our fake 'svn-create-patch' returns $PWD instead of a patch, check that it was executed from the root of the repo. |
|
539 self.assertEqual("%s\n" % os.path.realpath(scm.checkout_root), patch_contents) # Add a \n because echo adds a \n. |
|
540 |
|
541 def test_detection(self): |
|
542 scm = detect_scm_system(self.svn_checkout_path) |
|
543 self.assertEqual(scm.display_name(), "svn") |
|
544 self.assertEqual(scm.supports_local_commits(), False) |
|
545 |
|
546 def test_apply_small_binary_patch(self): |
|
547 patch_contents = """Index: test_file.swf |
|
548 =================================================================== |
|
549 Cannot display: file marked as a binary type. |
|
550 svn:mime-type = application/octet-stream |
|
551 |
|
552 Property changes on: test_file.swf |
|
553 ___________________________________________________________________ |
|
554 Name: svn:mime-type |
|
555 + application/octet-stream |
|
556 |
|
557 |
|
558 Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== |
|
559 """ |
|
560 expected_contents = base64.b64decode("Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==") |
|
561 self._setup_webkittools_scripts_symlink(self.scm) |
|
562 patch_file = self._create_patch(patch_contents) |
|
563 self.checkout.apply_patch(patch_file) |
|
564 actual_contents = read_from_path("test_file.swf", encoding=None) |
|
565 self.assertEqual(actual_contents, expected_contents) |
|
566 |
|
567 def test_apply_svn_patch(self): |
|
568 scm = detect_scm_system(self.svn_checkout_path) |
|
569 patch = self._create_patch(_svn_diff("-r5:4")) |
|
570 self._setup_webkittools_scripts_symlink(scm) |
|
571 Checkout(scm).apply_patch(patch) |
|
572 |
|
573 def test_apply_svn_patch_force(self): |
|
574 scm = detect_scm_system(self.svn_checkout_path) |
|
575 patch = self._create_patch(_svn_diff("-r3:5")) |
|
576 self._setup_webkittools_scripts_symlink(scm) |
|
577 self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True) |
|
578 |
|
579 def test_commit_logs(self): |
|
580 # Commits have dates and usernames in them, so we can't just direct compare. |
|
581 self.assertTrue(re.search('fourth commit', self.scm.last_svn_commit_log())) |
|
582 self.assertTrue(re.search('second commit', self.scm.svn_commit_log(3))) |
|
583 |
|
584 def _shared_test_commit_with_message(self, username=None): |
|
585 write_into_file_at_path('test_file', 'more test content') |
|
586 commit_text = self.scm.commit_with_message("another test commit", username) |
|
587 self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') |
|
588 |
|
589 self.scm.dryrun = True |
|
590 write_into_file_at_path('test_file', 'still more test content') |
|
591 commit_text = self.scm.commit_with_message("yet another test commit", username) |
|
592 self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0') |
|
593 |
|
594 def test_commit_text_parsing(self): |
|
595 self._shared_test_commit_with_message() |
|
596 |
|
597 def test_commit_with_username(self): |
|
598 self._shared_test_commit_with_message("dbates@webkit.org") |
|
599 |
|
600 def test_commit_without_authorization(self): |
|
601 self.scm.has_authorization_for_realm = lambda: False |
|
602 self.assertRaises(AuthenticationError, self._shared_test_commit_with_message) |
|
603 |
|
604 def test_has_authorization_for_realm(self): |
|
605 scm = detect_scm_system(self.svn_checkout_path) |
|
606 fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") |
|
607 svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") |
|
608 os.mkdir(svn_config_dir_path) |
|
609 fake_webkit_auth_file = os.path.join(svn_config_dir_path, "fake_webkit_auth_file") |
|
610 write_into_file_at_path(fake_webkit_auth_file, SVN.svn_server_realm) |
|
611 self.assertTrue(scm.has_authorization_for_realm(home_directory=fake_home_dir)) |
|
612 os.remove(fake_webkit_auth_file) |
|
613 os.rmdir(svn_config_dir_path) |
|
614 os.rmdir(fake_home_dir) |
|
615 |
|
616 def test_not_have_authorization_for_realm(self): |
|
617 scm = detect_scm_system(self.svn_checkout_path) |
|
618 fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") |
|
619 svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") |
|
620 os.mkdir(svn_config_dir_path) |
|
621 self.assertFalse(scm.has_authorization_for_realm(home_directory=fake_home_dir)) |
|
622 os.rmdir(svn_config_dir_path) |
|
623 os.rmdir(fake_home_dir) |
|
624 |
|
625 def test_reverse_diff(self): |
|
626 self._shared_test_reverse_diff() |
|
627 |
|
628 def test_diff_for_revision(self): |
|
629 self._shared_test_diff_for_revision() |
|
630 |
|
631 def test_svn_apply_git_patch(self): |
|
632 self._shared_test_svn_apply_git_patch() |
|
633 |
|
634 def test_changed_files(self): |
|
635 self._shared_test_changed_files() |
|
636 |
|
637 def test_changed_files_for_revision(self): |
|
638 self._shared_test_changed_files_for_revision() |
|
639 |
|
640 def test_added_files(self): |
|
641 self._shared_test_added_files() |
|
642 |
|
643 def test_contents_at_revision(self): |
|
644 self._shared_test_contents_at_revision() |
|
645 |
|
646 def test_committer_email_for_revision(self): |
|
647 self._shared_test_committer_email_for_revision() |
|
648 |
|
649 def test_add_recursively(self): |
|
650 self._shared_test_add_recursively() |
|
651 |
|
652 def test_delete(self): |
|
653 os.chdir(self.svn_checkout_path) |
|
654 self.scm.delete("test_file") |
|
655 self.assertTrue("test_file" in self.scm.deleted_files()) |
|
656 |
|
657 def test_propset_propget(self): |
|
658 filepath = os.path.join(self.svn_checkout_path, "test_file") |
|
659 expected_mime_type = "x-application/foo-bar" |
|
660 self.scm.propset("svn:mime-type", expected_mime_type, filepath) |
|
661 self.assertEqual(expected_mime_type, self.scm.propget("svn:mime-type", filepath)) |
|
662 |
|
663 def test_show_head(self): |
|
664 write_into_file_at_path("test_file", u"Hello!", "utf-8") |
|
665 SVNTestRepository._svn_commit("fourth commit") |
|
666 self.assertEqual("Hello!", self.scm.show_head('test_file')) |
|
667 |
|
668 def test_show_head_binary(self): |
|
669 data = "\244" |
|
670 write_into_file_at_path("binary_file", data, encoding=None) |
|
671 self.scm.add("binary_file") |
|
672 self.scm.commit_with_message("a test commit") |
|
673 self.assertEqual(data, self.scm.show_head('binary_file')) |
|
674 |
|
675 def do_test_diff_for_file(self): |
|
676 write_into_file_at_path('test_file', 'some content') |
|
677 self.scm.commit_with_message("a test commit") |
|
678 diff = self.scm.diff_for_file('test_file') |
|
679 self.assertEqual(diff, "") |
|
680 |
|
681 write_into_file_at_path("test_file", "changed content") |
|
682 diff = self.scm.diff_for_file('test_file') |
|
683 self.assertTrue("-some content" in diff) |
|
684 self.assertTrue("+changed content" in diff) |
|
685 |
|
686 def clean_bogus_dir(self): |
|
687 self.bogus_dir = self.scm._bogus_dir_name() |
|
688 if os.path.exists(self.bogus_dir): |
|
689 shutil.rmtree(self.bogus_dir) |
|
690 |
|
691 def test_diff_for_file_with_existing_bogus_dir(self): |
|
692 self.clean_bogus_dir() |
|
693 os.mkdir(self.bogus_dir) |
|
694 self.do_test_diff_for_file() |
|
695 self.assertTrue(os.path.exists(self.bogus_dir)) |
|
696 shutil.rmtree(self.bogus_dir) |
|
697 |
|
698 def test_diff_for_file_with_missing_bogus_dir(self): |
|
699 self.clean_bogus_dir() |
|
700 self.do_test_diff_for_file() |
|
701 self.assertFalse(os.path.exists(self.bogus_dir)) |
|
702 |
|
703 |
|
704 class GitTest(SCMTest): |
|
705 |
|
706 def setUp(self): |
|
707 """Sets up fresh git repository with one commit. Then setups a second git |
|
708 repo that tracks the first one.""" |
|
709 self.original_dir = os.getcwd() |
|
710 |
|
711 self.untracking_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout2") |
|
712 run_command(['git', 'init', self.untracking_checkout_path]) |
|
713 |
|
714 os.chdir(self.untracking_checkout_path) |
|
715 write_into_file_at_path('foo_file', 'foo') |
|
716 run_command(['git', 'add', 'foo_file']) |
|
717 run_command(['git', 'commit', '-am', 'dummy commit']) |
|
718 self.untracking_scm = detect_scm_system(self.untracking_checkout_path) |
|
719 |
|
720 self.tracking_git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") |
|
721 run_command(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) |
|
722 os.chdir(self.tracking_git_checkout_path) |
|
723 self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) |
|
724 |
|
725 def tearDown(self): |
|
726 # Change back to a valid directory so that later calls to os.getcwd() do not fail. |
|
727 os.chdir(self.original_dir) |
|
728 run_command(['rm', '-rf', self.tracking_git_checkout_path]) |
|
729 run_command(['rm', '-rf', self.untracking_checkout_path]) |
|
730 |
|
731 def test_remote_branch_ref(self): |
|
732 self.assertEqual(self.tracking_scm.remote_branch_ref(), 'refs/remotes/origin/master') |
|
733 |
|
734 os.chdir(self.untracking_checkout_path) |
|
735 self.assertRaises(ScriptError, self.untracking_scm.remote_branch_ref) |
|
736 |
|
737 |
|
738 class GitSVNTest(SCMTest): |
|
739 |
|
740 def _setup_git_checkout(self): |
|
741 self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") |
|
742 # --quiet doesn't make git svn silent, so we use run_silent to redirect output |
|
743 run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) |
|
744 os.chdir(self.git_checkout_path) |
|
745 |
|
746 def _tear_down_git_checkout(self): |
|
747 # Change back to a valid directory so that later calls to os.getcwd() do not fail. |
|
748 os.chdir(self.original_dir) |
|
749 run_command(['rm', '-rf', self.git_checkout_path]) |
|
750 |
|
751 def setUp(self): |
|
752 self.original_dir = os.getcwd() |
|
753 |
|
754 SVNTestRepository.setup(self) |
|
755 self._setup_git_checkout() |
|
756 self.scm = detect_scm_system(self.git_checkout_path) |
|
757 # For historical reasons, we test some checkout code here too. |
|
758 self.checkout = Checkout(self.scm) |
|
759 |
|
760 def tearDown(self): |
|
761 SVNTestRepository.tear_down(self) |
|
762 self._tear_down_git_checkout() |
|
763 |
|
764 def test_detection(self): |
|
765 scm = detect_scm_system(self.git_checkout_path) |
|
766 self.assertEqual(scm.display_name(), "git") |
|
767 self.assertEqual(scm.supports_local_commits(), True) |
|
768 |
|
769 def test_read_git_config(self): |
|
770 key = 'test.git-config' |
|
771 value = 'git-config value' |
|
772 run_command(['git', 'config', key, value]) |
|
773 self.assertEqual(self.scm.read_git_config(key), value) |
|
774 |
|
775 def test_local_commits(self): |
|
776 test_file = os.path.join(self.git_checkout_path, 'test_file') |
|
777 write_into_file_at_path(test_file, 'foo') |
|
778 run_command(['git', 'commit', '-a', '-m', 'local commit']) |
|
779 |
|
780 self.assertEqual(len(self.scm.local_commits()), 1) |
|
781 |
|
782 def test_discard_local_commits(self): |
|
783 test_file = os.path.join(self.git_checkout_path, 'test_file') |
|
784 write_into_file_at_path(test_file, 'foo') |
|
785 run_command(['git', 'commit', '-a', '-m', 'local commit']) |
|
786 |
|
787 self.assertEqual(len(self.scm.local_commits()), 1) |
|
788 self.scm.discard_local_commits() |
|
789 self.assertEqual(len(self.scm.local_commits()), 0) |
|
790 |
|
791 def test_delete_branch(self): |
|
792 new_branch = 'foo' |
|
793 |
|
794 run_command(['git', 'checkout', '-b', new_branch]) |
|
795 self.assertEqual(run_command(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) |
|
796 |
|
797 run_command(['git', 'checkout', '-b', 'bar']) |
|
798 self.scm.delete_branch(new_branch) |
|
799 |
|
800 self.assertFalse(re.search(r'foo', run_command(['git', 'branch']))) |
|
801 |
|
802 def test_remote_merge_base(self): |
|
803 # Diff to merge-base should include working-copy changes, |
|
804 # which the diff to svn_branch.. doesn't. |
|
805 test_file = os.path.join(self.git_checkout_path, 'test_file') |
|
806 write_into_file_at_path(test_file, 'foo') |
|
807 |
|
808 diff_to_common_base = _git_diff(self.scm.remote_branch_ref() + '..') |
|
809 diff_to_merge_base = _git_diff(self.scm.remote_merge_base()) |
|
810 |
|
811 self.assertFalse(re.search(r'foo', diff_to_common_base)) |
|
812 self.assertTrue(re.search(r'foo', diff_to_merge_base)) |
|
813 |
|
814 def test_rebase_in_progress(self): |
|
815 svn_test_file = os.path.join(self.svn_checkout_path, 'test_file') |
|
816 write_into_file_at_path(svn_test_file, "svn_checkout") |
|
817 run_command(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) |
|
818 |
|
819 git_test_file = os.path.join(self.git_checkout_path, 'test_file') |
|
820 write_into_file_at_path(git_test_file, "git_checkout") |
|
821 run_command(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) |
|
822 |
|
823 # --quiet doesn't make git svn silent, so use run_silent to redirect output |
|
824 self.assertRaises(ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase']) # Will fail due to a conflict leaving us mid-rebase. |
|
825 |
|
826 scm = detect_scm_system(self.git_checkout_path) |
|
827 self.assertTrue(scm.rebase_in_progress()) |
|
828 |
|
829 # Make sure our cleanup works. |
|
830 scm.clean_working_directory() |
|
831 self.assertFalse(scm.rebase_in_progress()) |
|
832 |
|
833 # Make sure cleanup doesn't throw when no rebase is in progress. |
|
834 scm.clean_working_directory() |
|
835 |
|
836 def test_commitish_parsing(self): |
|
837 scm = detect_scm_system(self.git_checkout_path) |
|
838 |
|
839 # Multiple revisions are cherry-picked. |
|
840 self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1) |
|
841 self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2) |
|
842 |
|
843 # ... is an invalid range specifier |
|
844 self.assertRaises(ScriptError, scm.commit_ids_from_commitish_arguments, ['trunk...HEAD']) |
|
845 |
|
846 def test_commitish_order(self): |
|
847 scm = detect_scm_system(self.git_checkout_path) |
|
848 |
|
849 commit_range = 'HEAD~3..HEAD' |
|
850 |
|
851 actual_commits = scm.commit_ids_from_commitish_arguments([commit_range]) |
|
852 expected_commits = [] |
|
853 expected_commits += reversed(run_command(['git', 'rev-list', commit_range]).splitlines()) |
|
854 |
|
855 self.assertEqual(actual_commits, expected_commits) |
|
856 |
|
857 def test_apply_git_patch(self): |
|
858 scm = detect_scm_system(self.git_checkout_path) |
|
859 # We carefullly pick a diff which does not have a directory addition |
|
860 # as currently svn-apply will error out when trying to remove directories |
|
861 # in Git: https://bugs.webkit.org/show_bug.cgi?id=34871 |
|
862 patch = self._create_patch(_git_diff('HEAD..HEAD^')) |
|
863 self._setup_webkittools_scripts_symlink(scm) |
|
864 Checkout(scm).apply_patch(patch) |
|
865 |
|
866 def test_apply_git_patch_force(self): |
|
867 scm = detect_scm_system(self.git_checkout_path) |
|
868 patch = self._create_patch(_git_diff('HEAD~2..HEAD')) |
|
869 self._setup_webkittools_scripts_symlink(scm) |
|
870 self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True) |
|
871 |
|
872 def test_commit_text_parsing(self): |
|
873 write_into_file_at_path('test_file', 'more test content') |
|
874 commit_text = self.scm.commit_with_message("another test commit") |
|
875 self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') |
|
876 |
|
877 self.scm.dryrun = True |
|
878 write_into_file_at_path('test_file', 'still more test content') |
|
879 commit_text = self.scm.commit_with_message("yet another test commit") |
|
880 self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0') |
|
881 |
|
882 def test_commit_with_message_working_copy_only(self): |
|
883 write_into_file_at_path('test_file_commit1', 'more test content') |
|
884 run_command(['git', 'add', 'test_file_commit1']) |
|
885 scm = detect_scm_system(self.git_checkout_path) |
|
886 commit_text = scm.commit_with_message("yet another test commit") |
|
887 |
|
888 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
889 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
890 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
891 |
|
892 def _one_local_commit(self): |
|
893 write_into_file_at_path('test_file_commit1', 'more test content') |
|
894 run_command(['git', 'add', 'test_file_commit1']) |
|
895 self.scm.commit_locally_with_message("another test commit") |
|
896 |
|
897 def _one_local_commit_plus_working_copy_changes(self): |
|
898 self._one_local_commit() |
|
899 write_into_file_at_path('test_file_commit2', 'still more test content') |
|
900 run_command(['git', 'add', 'test_file_commit2']) |
|
901 |
|
902 def _two_local_commits(self): |
|
903 self._one_local_commit() |
|
904 write_into_file_at_path('test_file_commit2', 'still more test content') |
|
905 run_command(['git', 'add', 'test_file_commit2']) |
|
906 self.scm.commit_locally_with_message("yet another test commit") |
|
907 |
|
908 def _three_local_commits(self): |
|
909 write_into_file_at_path('test_file_commit0', 'more test content') |
|
910 run_command(['git', 'add', 'test_file_commit0']) |
|
911 self.scm.commit_locally_with_message("another test commit") |
|
912 self._two_local_commits() |
|
913 |
|
914 def test_commit_with_message(self): |
|
915 self._one_local_commit_plus_working_copy_changes() |
|
916 scm = detect_scm_system(self.git_checkout_path) |
|
917 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit") |
|
918 commit_text = scm.commit_with_message("yet another test commit", force_squash=True) |
|
919 |
|
920 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
921 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
922 self.assertTrue(re.search(r'test_file_commit2', svn_log)) |
|
923 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
924 |
|
925 def test_commit_with_message_git_commit(self): |
|
926 self._two_local_commits() |
|
927 |
|
928 scm = detect_scm_system(self.git_checkout_path) |
|
929 commit_text = scm.commit_with_message("another test commit", git_commit="HEAD^") |
|
930 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
931 |
|
932 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
933 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
934 self.assertFalse(re.search(r'test_file_commit2', svn_log)) |
|
935 |
|
936 def test_commit_with_message_git_commit_range(self): |
|
937 self._three_local_commits() |
|
938 |
|
939 scm = detect_scm_system(self.git_checkout_path) |
|
940 commit_text = scm.commit_with_message("another test commit", git_commit="HEAD~2..HEAD") |
|
941 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
942 |
|
943 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
944 self.assertFalse(re.search(r'test_file_commit0', svn_log)) |
|
945 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
946 self.assertTrue(re.search(r'test_file_commit2', svn_log)) |
|
947 |
|
948 def test_changed_files_working_copy_only(self): |
|
949 self._one_local_commit_plus_working_copy_changes() |
|
950 scm = detect_scm_system(self.git_checkout_path) |
|
951 commit_text = scm.commit_with_message("another test commit", git_commit="HEAD..") |
|
952 self.assertFalse(re.search(r'test_file_commit1', svn_log)) |
|
953 self.assertTrue(re.search(r'test_file_commit2', svn_log)) |
|
954 |
|
955 def test_commit_with_message_only_local_commit(self): |
|
956 self._one_local_commit() |
|
957 scm = detect_scm_system(self.git_checkout_path) |
|
958 commit_text = scm.commit_with_message("another test commit") |
|
959 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
960 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
961 |
|
962 def test_commit_with_message_multiple_local_commits_and_working_copy(self): |
|
963 self._two_local_commits() |
|
964 write_into_file_at_path('test_file_commit1', 'working copy change') |
|
965 scm = detect_scm_system(self.git_checkout_path) |
|
966 |
|
967 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit") |
|
968 commit_text = scm.commit_with_message("another test commit", force_squash=True) |
|
969 |
|
970 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
971 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
972 self.assertTrue(re.search(r'test_file_commit2', svn_log)) |
|
973 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
974 |
|
975 def test_commit_with_message_git_commit_and_working_copy(self): |
|
976 self._two_local_commits() |
|
977 write_into_file_at_path('test_file_commit1', 'working copy change') |
|
978 scm = detect_scm_system(self.git_checkout_path) |
|
979 self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", git_commit="HEAD^") |
|
980 |
|
981 def test_commit_with_message_multiple_local_commits_always_squash(self): |
|
982 self._two_local_commits() |
|
983 scm = detect_scm_system(self.git_checkout_path) |
|
984 scm._assert_can_squash = lambda working_directory_is_clean: True |
|
985 commit_text = scm.commit_with_message("yet another test commit") |
|
986 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
987 |
|
988 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
989 self.assertTrue(re.search(r'test_file_commit2', svn_log)) |
|
990 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
991 |
|
992 def test_commit_with_message_multiple_local_commits(self): |
|
993 self._two_local_commits() |
|
994 scm = detect_scm_system(self.git_checkout_path) |
|
995 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit") |
|
996 commit_text = scm.commit_with_message("yet another test commit", force_squash=True) |
|
997 |
|
998 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') |
|
999 |
|
1000 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) |
|
1001 self.assertTrue(re.search(r'test_file_commit2', svn_log)) |
|
1002 self.assertTrue(re.search(r'test_file_commit1', svn_log)) |
|
1003 |
|
1004 def test_commit_with_message_not_synced(self): |
|
1005 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) |
|
1006 self._two_local_commits() |
|
1007 scm = detect_scm_system(self.git_checkout_path) |
|
1008 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit") |
|
1009 self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", force_squash=True) |
|
1010 |
|
1011 def test_remote_branch_ref(self): |
|
1012 self.assertEqual(self.scm.remote_branch_ref(), 'refs/remotes/trunk') |
|
1013 |
|
1014 def test_reverse_diff(self): |
|
1015 self._shared_test_reverse_diff() |
|
1016 |
|
1017 def test_diff_for_revision(self): |
|
1018 self._shared_test_diff_for_revision() |
|
1019 |
|
1020 def test_svn_apply_git_patch(self): |
|
1021 self._shared_test_svn_apply_git_patch() |
|
1022 |
|
1023 def test_create_patch_local_plus_working_copy(self): |
|
1024 self._one_local_commit_plus_working_copy_changes() |
|
1025 scm = detect_scm_system(self.git_checkout_path) |
|
1026 patch = scm.create_patch() |
|
1027 self.assertTrue(re.search(r'test_file_commit1', patch)) |
|
1028 self.assertTrue(re.search(r'test_file_commit2', patch)) |
|
1029 |
|
1030 def test_create_patch(self): |
|
1031 self._one_local_commit_plus_working_copy_changes() |
|
1032 scm = detect_scm_system(self.git_checkout_path) |
|
1033 patch = scm.create_patch() |
|
1034 self.assertTrue(re.search(r'test_file_commit2', patch)) |
|
1035 self.assertTrue(re.search(r'test_file_commit1', patch)) |
|
1036 |
|
1037 def test_create_patch_git_commit(self): |
|
1038 self._two_local_commits() |
|
1039 scm = detect_scm_system(self.git_checkout_path) |
|
1040 patch = scm.create_patch(git_commit="HEAD^") |
|
1041 self.assertTrue(re.search(r'test_file_commit1', patch)) |
|
1042 self.assertFalse(re.search(r'test_file_commit2', patch)) |
|
1043 |
|
1044 def test_create_patch_git_commit_range(self): |
|
1045 self._three_local_commits() |
|
1046 scm = detect_scm_system(self.git_checkout_path) |
|
1047 patch = scm.create_patch(git_commit="HEAD~2..HEAD") |
|
1048 self.assertFalse(re.search(r'test_file_commit0', patch)) |
|
1049 self.assertTrue(re.search(r'test_file_commit2', patch)) |
|
1050 self.assertTrue(re.search(r'test_file_commit1', patch)) |
|
1051 |
|
1052 def test_create_patch_working_copy_only(self): |
|
1053 self._one_local_commit_plus_working_copy_changes() |
|
1054 scm = detect_scm_system(self.git_checkout_path) |
|
1055 patch = scm.create_patch(git_commit="HEAD..") |
|
1056 self.assertFalse(re.search(r'test_file_commit1', patch)) |
|
1057 self.assertTrue(re.search(r'test_file_commit2', patch)) |
|
1058 |
|
1059 def test_create_patch_multiple_local_commits(self): |
|
1060 self._two_local_commits() |
|
1061 scm = detect_scm_system(self.git_checkout_path) |
|
1062 patch = scm.create_patch() |
|
1063 self.assertTrue(re.search(r'test_file_commit2', patch)) |
|
1064 self.assertTrue(re.search(r'test_file_commit1', patch)) |
|
1065 |
|
1066 def test_create_patch_not_synced(self): |
|
1067 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) |
|
1068 self._two_local_commits() |
|
1069 scm = detect_scm_system(self.git_checkout_path) |
|
1070 self.assertRaises(ScriptError, scm.create_patch) |
|
1071 |
|
1072 def test_create_binary_patch(self): |
|
1073 # Create a git binary patch and check the contents. |
|
1074 scm = detect_scm_system(self.git_checkout_path) |
|
1075 test_file_name = 'binary_file' |
|
1076 test_file_path = os.path.join(self.git_checkout_path, test_file_name) |
|
1077 file_contents = ''.join(map(chr, range(256))) |
|
1078 write_into_file_at_path(test_file_path, file_contents, encoding=None) |
|
1079 run_command(['git', 'add', test_file_name]) |
|
1080 patch = scm.create_patch() |
|
1081 self.assertTrue(re.search(r'\nliteral 0\n', patch)) |
|
1082 self.assertTrue(re.search(r'\nliteral 256\n', patch)) |
|
1083 |
|
1084 # Check if we can apply the created patch. |
|
1085 run_command(['git', 'rm', '-f', test_file_name]) |
|
1086 self._setup_webkittools_scripts_symlink(scm) |
|
1087 self.checkout.apply_patch(self._create_patch(patch)) |
|
1088 self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None)) |
|
1089 |
|
1090 # Check if we can create a patch from a local commit. |
|
1091 write_into_file_at_path(test_file_path, file_contents, encoding=None) |
|
1092 run_command(['git', 'add', test_file_name]) |
|
1093 run_command(['git', 'commit', '-m', 'binary diff']) |
|
1094 patch_from_local_commit = scm.create_patch('HEAD') |
|
1095 self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit)) |
|
1096 self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit)) |
|
1097 |
|
1098 def test_changed_files_local_plus_working_copy(self): |
|
1099 self._one_local_commit_plus_working_copy_changes() |
|
1100 scm = detect_scm_system(self.git_checkout_path) |
|
1101 files = scm.changed_files() |
|
1102 self.assertTrue('test_file_commit1' in files) |
|
1103 self.assertTrue('test_file_commit2' in files) |
|
1104 |
|
1105 def test_changed_files_git_commit(self): |
|
1106 self._two_local_commits() |
|
1107 scm = detect_scm_system(self.git_checkout_path) |
|
1108 files = scm.changed_files(git_commit="HEAD^") |
|
1109 self.assertTrue('test_file_commit1' in files) |
|
1110 self.assertFalse('test_file_commit2' in files) |
|
1111 |
|
1112 def test_changed_files_git_commit_range(self): |
|
1113 self._three_local_commits() |
|
1114 scm = detect_scm_system(self.git_checkout_path) |
|
1115 files = scm.changed_files(git_commit="HEAD~2..HEAD") |
|
1116 self.assertTrue('test_file_commit0' not in files) |
|
1117 self.assertTrue('test_file_commit1' in files) |
|
1118 self.assertTrue('test_file_commit2' in files) |
|
1119 |
|
1120 def test_changed_files_working_copy_only(self): |
|
1121 self._one_local_commit_plus_working_copy_changes() |
|
1122 scm = detect_scm_system(self.git_checkout_path) |
|
1123 files = scm.changed_files(git_commit="HEAD..") |
|
1124 self.assertFalse('test_file_commit1' in files) |
|
1125 self.assertTrue('test_file_commit2' in files) |
|
1126 |
|
1127 def test_changed_files_multiple_local_commits(self): |
|
1128 self._two_local_commits() |
|
1129 scm = detect_scm_system(self.git_checkout_path) |
|
1130 files = scm.changed_files() |
|
1131 self.assertTrue('test_file_commit2' in files) |
|
1132 self.assertTrue('test_file_commit1' in files) |
|
1133 |
|
1134 def test_changed_files_not_synced(self): |
|
1135 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) |
|
1136 self._two_local_commits() |
|
1137 scm = detect_scm_system(self.git_checkout_path) |
|
1138 self.assertRaises(ScriptError, scm.changed_files) |
|
1139 |
|
1140 def test_changed_files(self): |
|
1141 self._shared_test_changed_files() |
|
1142 |
|
1143 def test_changed_files_for_revision(self): |
|
1144 self._shared_test_changed_files_for_revision() |
|
1145 |
|
1146 def test_contents_at_revision(self): |
|
1147 self._shared_test_contents_at_revision() |
|
1148 |
|
1149 def test_added_files(self): |
|
1150 self._shared_test_added_files() |
|
1151 |
|
1152 def test_committer_email_for_revision(self): |
|
1153 self._shared_test_committer_email_for_revision() |
|
1154 |
|
1155 def test_add_recursively(self): |
|
1156 self._shared_test_add_recursively() |
|
1157 |
|
1158 def test_delete(self): |
|
1159 self._two_local_commits() |
|
1160 self.scm.delete('test_file_commit1') |
|
1161 self.assertTrue("test_file_commit1" in self.scm.deleted_files()) |
|
1162 |
|
1163 def test_to_object_name(self): |
|
1164 relpath = 'test_file_commit1' |
|
1165 fullpath = os.path.join(self.git_checkout_path, relpath) |
|
1166 self._two_local_commits() |
|
1167 self.assertEqual(relpath, self.scm.to_object_name(fullpath)) |
|
1168 |
|
1169 def test_show_head(self): |
|
1170 self._two_local_commits() |
|
1171 self.assertEqual("more test content", self.scm.show_head('test_file_commit1')) |
|
1172 |
|
1173 def test_show_head_binary(self): |
|
1174 self._two_local_commits() |
|
1175 data = "\244" |
|
1176 write_into_file_at_path("binary_file", data, encoding=None) |
|
1177 self.scm.add("binary_file") |
|
1178 self.scm.commit_locally_with_message("a test commit") |
|
1179 self.assertEqual(data, self.scm.show_head('binary_file')) |
|
1180 |
|
1181 def test_diff_for_file(self): |
|
1182 self._two_local_commits() |
|
1183 write_into_file_at_path('test_file_commit1', "Updated", encoding=None) |
|
1184 |
|
1185 diff = self.scm.diff_for_file('test_file_commit1') |
|
1186 cached_diff = self.scm.diff_for_file('test_file_commit1') |
|
1187 self.assertTrue("+Updated" in diff) |
|
1188 self.assertTrue("-more test content" in diff) |
|
1189 |
|
1190 self.scm.add('test_file_commit1') |
|
1191 |
|
1192 cached_diff = self.scm.diff_for_file('test_file_commit1') |
|
1193 self.assertTrue("+Updated" in cached_diff) |
|
1194 self.assertTrue("-more test content" in cached_diff) |
|
1195 |
|
1196 if __name__ == '__main__': |
|
1197 unittest.main() |