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