scm_unittest.py revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
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_text_parsing(self): 654 self._shared_test_commit_with_message() 655 656 def test_commit_with_username(self): 657 self._shared_test_commit_with_message("dbates@webkit.org") 658 659 def test_commit_without_authorization(self): 660 self.scm.has_authorization_for_realm = lambda: False 661 self.assertRaises(AuthenticationError, self._shared_test_commit_with_message) 662 663 def test_has_authorization_for_realm(self): 664 scm = detect_scm_system(self.svn_checkout_path) 665 fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") 666 svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") 667 os.mkdir(svn_config_dir_path) 668 fake_webkit_auth_file = os.path.join(svn_config_dir_path, "fake_webkit_auth_file") 669 write_into_file_at_path(fake_webkit_auth_file, SVN.svn_server_realm) 670 self.assertTrue(scm.has_authorization_for_realm(home_directory=fake_home_dir)) 671 os.remove(fake_webkit_auth_file) 672 os.rmdir(svn_config_dir_path) 673 os.rmdir(fake_home_dir) 674 675 def test_not_have_authorization_for_realm(self): 676 scm = detect_scm_system(self.svn_checkout_path) 677 fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") 678 svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") 679 os.mkdir(svn_config_dir_path) 680 self.assertFalse(scm.has_authorization_for_realm(home_directory=fake_home_dir)) 681 os.rmdir(svn_config_dir_path) 682 os.rmdir(fake_home_dir) 683 684 def test_reverse_diff(self): 685 self._shared_test_reverse_diff() 686 687 def test_diff_for_revision(self): 688 self._shared_test_diff_for_revision() 689 690 def test_svn_apply_git_patch(self): 691 self._shared_test_svn_apply_git_patch() 692 693 def test_changed_files(self): 694 self._shared_test_changed_files() 695 696 def test_changed_files_for_revision(self): 697 self._shared_test_changed_files_for_revision() 698 699 def test_added_files(self): 700 self._shared_test_added_files() 701 702 def test_contents_at_revision(self): 703 self._shared_test_contents_at_revision() 704 705 def test_revisions_changing_file(self): 706 self._shared_test_revisions_changing_file() 707 708 def test_committer_email_for_revision(self): 709 self._shared_test_committer_email_for_revision() 710 711 def test_add_recursively(self): 712 self._shared_test_add_recursively() 713 714 def test_delete(self): 715 os.chdir(self.svn_checkout_path) 716 self.scm.delete("test_file") 717 self.assertTrue("test_file" in self.scm.deleted_files()) 718 719 def test_propset_propget(self): 720 filepath = os.path.join(self.svn_checkout_path, "test_file") 721 expected_mime_type = "x-application/foo-bar" 722 self.scm.propset("svn:mime-type", expected_mime_type, filepath) 723 self.assertEqual(expected_mime_type, self.scm.propget("svn:mime-type", filepath)) 724 725 def test_show_head(self): 726 write_into_file_at_path("test_file", u"Hello!", "utf-8") 727 SVNTestRepository._svn_commit("fourth commit") 728 self.assertEqual("Hello!", self.scm.show_head('test_file')) 729 730 def test_show_head_binary(self): 731 data = "\244" 732 write_into_file_at_path("binary_file", data, encoding=None) 733 self.scm.add("binary_file") 734 self.scm.commit_with_message("a test commit") 735 self.assertEqual(data, self.scm.show_head('binary_file')) 736 737 def do_test_diff_for_file(self): 738 write_into_file_at_path('test_file', 'some content') 739 self.scm.commit_with_message("a test commit") 740 diff = self.scm.diff_for_file('test_file') 741 self.assertEqual(diff, "") 742 743 write_into_file_at_path("test_file", "changed content") 744 diff = self.scm.diff_for_file('test_file') 745 self.assertTrue("-some content" in diff) 746 self.assertTrue("+changed content" in diff) 747 748 def clean_bogus_dir(self): 749 self.bogus_dir = self.scm._bogus_dir_name() 750 if os.path.exists(self.bogus_dir): 751 shutil.rmtree(self.bogus_dir) 752 753 def test_diff_for_file_with_existing_bogus_dir(self): 754 self.clean_bogus_dir() 755 os.mkdir(self.bogus_dir) 756 self.do_test_diff_for_file() 757 self.assertTrue(os.path.exists(self.bogus_dir)) 758 shutil.rmtree(self.bogus_dir) 759 760 def test_diff_for_file_with_missing_bogus_dir(self): 761 self.clean_bogus_dir() 762 self.do_test_diff_for_file() 763 self.assertFalse(os.path.exists(self.bogus_dir)) 764 765 def test_svn_lock(self): 766 svn_root_lock_path = ".svn/lock" 767 write_into_file_at_path(svn_root_lock_path, "", "utf-8") 768 # webkit-patch uses a Checkout object and runs update-webkit, just use svn update here. 769 self.assertRaises(ScriptError, run_command, ['svn', 'update']) 770 self.scm.clean_working_directory() 771 self.assertFalse(os.path.exists(svn_root_lock_path)) 772 run_command(['svn', 'update']) # Should succeed and not raise. 773 774 775class GitTest(SCMTest): 776 777 def setUp(self): 778 """Sets up fresh git repository with one commit. Then setups a second git 779 repo that tracks the first one.""" 780 self.original_dir = os.getcwd() 781 782 self.untracking_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout2") 783 run_command(['git', 'init', self.untracking_checkout_path]) 784 785 os.chdir(self.untracking_checkout_path) 786 write_into_file_at_path('foo_file', 'foo') 787 run_command(['git', 'add', 'foo_file']) 788 run_command(['git', 'commit', '-am', 'dummy commit']) 789 self.untracking_scm = detect_scm_system(self.untracking_checkout_path) 790 791 self.tracking_git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") 792 run_command(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) 793 os.chdir(self.tracking_git_checkout_path) 794 self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) 795 796 def tearDown(self): 797 # Change back to a valid directory so that later calls to os.getcwd() do not fail. 798 os.chdir(self.original_dir) 799 run_command(['rm', '-rf', self.tracking_git_checkout_path]) 800 run_command(['rm', '-rf', self.untracking_checkout_path]) 801 802 def test_remote_branch_ref(self): 803 self.assertEqual(self.tracking_scm.remote_branch_ref(), 'refs/remotes/origin/master') 804 805 os.chdir(self.untracking_checkout_path) 806 self.assertRaises(ScriptError, self.untracking_scm.remote_branch_ref) 807 808 def test_multiple_remotes(self): 809 run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1']) 810 run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) 811 self.assertEqual(self.tracking_scm.remote_branch_ref(), 'remote1') 812 813class GitSVNTest(SCMTest): 814 815 def _setup_git_checkout(self): 816 self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") 817 # --quiet doesn't make git svn silent, so we use run_silent to redirect output 818 run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) 819 os.chdir(self.git_checkout_path) 820 821 def _tear_down_git_checkout(self): 822 # Change back to a valid directory so that later calls to os.getcwd() do not fail. 823 os.chdir(self.original_dir) 824 run_command(['rm', '-rf', self.git_checkout_path]) 825 826 def setUp(self): 827 self.original_dir = os.getcwd() 828 829 SVNTestRepository.setup(self) 830 self._setup_git_checkout() 831 self.scm = detect_scm_system(self.git_checkout_path) 832 # For historical reasons, we test some checkout code here too. 833 self.checkout = Checkout(self.scm) 834 835 def tearDown(self): 836 SVNTestRepository.tear_down(self) 837 self._tear_down_git_checkout() 838 839 def test_detection(self): 840 scm = detect_scm_system(self.git_checkout_path) 841 self.assertEqual(scm.display_name(), "git") 842 self.assertEqual(scm.supports_local_commits(), True) 843 844 def test_read_git_config(self): 845 key = 'test.git-config' 846 value = 'git-config value' 847 run_command(['git', 'config', key, value]) 848 self.assertEqual(self.scm.read_git_config(key), value) 849 850 def test_local_commits(self): 851 test_file = os.path.join(self.git_checkout_path, 'test_file') 852 write_into_file_at_path(test_file, 'foo') 853 run_command(['git', 'commit', '-a', '-m', 'local commit']) 854 855 self.assertEqual(len(self.scm.local_commits()), 1) 856 857 def test_discard_local_commits(self): 858 test_file = os.path.join(self.git_checkout_path, 'test_file') 859 write_into_file_at_path(test_file, 'foo') 860 run_command(['git', 'commit', '-a', '-m', 'local commit']) 861 862 self.assertEqual(len(self.scm.local_commits()), 1) 863 self.scm.discard_local_commits() 864 self.assertEqual(len(self.scm.local_commits()), 0) 865 866 def test_delete_branch(self): 867 new_branch = 'foo' 868 869 run_command(['git', 'checkout', '-b', new_branch]) 870 self.assertEqual(run_command(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) 871 872 run_command(['git', 'checkout', '-b', 'bar']) 873 self.scm.delete_branch(new_branch) 874 875 self.assertFalse(re.search(r'foo', run_command(['git', 'branch']))) 876 877 def test_remote_merge_base(self): 878 # Diff to merge-base should include working-copy changes, 879 # which the diff to svn_branch.. doesn't. 880 test_file = os.path.join(self.git_checkout_path, 'test_file') 881 write_into_file_at_path(test_file, 'foo') 882 883 diff_to_common_base = _git_diff(self.scm.remote_branch_ref() + '..') 884 diff_to_merge_base = _git_diff(self.scm.remote_merge_base()) 885 886 self.assertFalse(re.search(r'foo', diff_to_common_base)) 887 self.assertTrue(re.search(r'foo', diff_to_merge_base)) 888 889 def test_rebase_in_progress(self): 890 svn_test_file = os.path.join(self.svn_checkout_path, 'test_file') 891 write_into_file_at_path(svn_test_file, "svn_checkout") 892 run_command(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) 893 894 git_test_file = os.path.join(self.git_checkout_path, 'test_file') 895 write_into_file_at_path(git_test_file, "git_checkout") 896 run_command(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) 897 898 # --quiet doesn't make git svn silent, so use run_silent to redirect output 899 self.assertRaises(ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase']) # Will fail due to a conflict leaving us mid-rebase. 900 901 scm = detect_scm_system(self.git_checkout_path) 902 self.assertTrue(scm.rebase_in_progress()) 903 904 # Make sure our cleanup works. 905 scm.clean_working_directory() 906 self.assertFalse(scm.rebase_in_progress()) 907 908 # Make sure cleanup doesn't throw when no rebase is in progress. 909 scm.clean_working_directory() 910 911 def test_commitish_parsing(self): 912 scm = detect_scm_system(self.git_checkout_path) 913 914 # Multiple revisions are cherry-picked. 915 self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1) 916 self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2) 917 918 # ... is an invalid range specifier 919 self.assertRaises(ScriptError, scm.commit_ids_from_commitish_arguments, ['trunk...HEAD']) 920 921 def test_commitish_order(self): 922 scm = detect_scm_system(self.git_checkout_path) 923 924 commit_range = 'HEAD~3..HEAD' 925 926 actual_commits = scm.commit_ids_from_commitish_arguments([commit_range]) 927 expected_commits = [] 928 expected_commits += reversed(run_command(['git', 'rev-list', commit_range]).splitlines()) 929 930 self.assertEqual(actual_commits, expected_commits) 931 932 def test_apply_git_patch(self): 933 scm = detect_scm_system(self.git_checkout_path) 934 # We carefullly pick a diff which does not have a directory addition 935 # as currently svn-apply will error out when trying to remove directories 936 # in Git: https://bugs.webkit.org/show_bug.cgi?id=34871 937 patch = self._create_patch(_git_diff('HEAD..HEAD^')) 938 self._setup_webkittools_scripts_symlink(scm) 939 Checkout(scm).apply_patch(patch) 940 941 def test_apply_git_patch_force(self): 942 scm = detect_scm_system(self.git_checkout_path) 943 patch = self._create_patch(_git_diff('HEAD~2..HEAD')) 944 self._setup_webkittools_scripts_symlink(scm) 945 self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True) 946 947 def test_commit_text_parsing(self): 948 write_into_file_at_path('test_file', 'more test content') 949 commit_text = self.scm.commit_with_message("another test commit") 950 self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') 951 952 self.scm.dryrun = True 953 write_into_file_at_path('test_file', 'still more test content') 954 commit_text = self.scm.commit_with_message("yet another test commit") 955 self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0') 956 957 def test_commit_with_message_working_copy_only(self): 958 write_into_file_at_path('test_file_commit1', 'more test content') 959 run_command(['git', 'add', 'test_file_commit1']) 960 scm = detect_scm_system(self.git_checkout_path) 961 commit_text = scm.commit_with_message("yet another test commit") 962 963 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 964 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 965 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 966 967 def _local_commit(self, filename, contents, message): 968 write_into_file_at_path(filename, contents) 969 run_command(['git', 'add', filename]) 970 self.scm.commit_locally_with_message(message) 971 972 def _one_local_commit(self): 973 self._local_commit('test_file_commit1', 'more test content', 'another test commit') 974 975 def _one_local_commit_plus_working_copy_changes(self): 976 self._one_local_commit() 977 write_into_file_at_path('test_file_commit2', 'still more test content') 978 run_command(['git', 'add', 'test_file_commit2']) 979 980 def _two_local_commits(self): 981 self._one_local_commit() 982 self._local_commit('test_file_commit2', 'still more test content', 'yet another test commit') 983 984 def _three_local_commits(self): 985 self._local_commit('test_file_commit0', 'more test content', 'another test commit') 986 self._two_local_commits() 987 988 def test_revisions_changing_files_with_local_commit(self): 989 self._one_local_commit() 990 self.assertEquals(self.scm.revisions_changing_file('test_file_commit1'), []) 991 992 def test_commit_with_message(self): 993 self._one_local_commit_plus_working_copy_changes() 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 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1000 self.assertTrue(re.search(r'test_file_commit2', svn_log)) 1001 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1002 1003 def test_commit_with_message_git_commit(self): 1004 self._two_local_commits() 1005 1006 scm = detect_scm_system(self.git_checkout_path) 1007 commit_text = scm.commit_with_message("another test commit", git_commit="HEAD^") 1008 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 1009 1010 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1011 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1012 self.assertFalse(re.search(r'test_file_commit2', svn_log)) 1013 1014 def test_commit_with_message_git_commit_range(self): 1015 self._three_local_commits() 1016 1017 scm = detect_scm_system(self.git_checkout_path) 1018 commit_text = scm.commit_with_message("another test commit", git_commit="HEAD~2..HEAD") 1019 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 1020 1021 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1022 self.assertFalse(re.search(r'test_file_commit0', svn_log)) 1023 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1024 self.assertTrue(re.search(r'test_file_commit2', svn_log)) 1025 1026 def test_changed_files_working_copy_only(self): 1027 self._one_local_commit_plus_working_copy_changes() 1028 scm = detect_scm_system(self.git_checkout_path) 1029 commit_text = scm.commit_with_message("another test commit", git_commit="HEAD..") 1030 self.assertFalse(re.search(r'test_file_commit1', svn_log)) 1031 self.assertTrue(re.search(r'test_file_commit2', svn_log)) 1032 1033 def test_commit_with_message_only_local_commit(self): 1034 self._one_local_commit() 1035 scm = detect_scm_system(self.git_checkout_path) 1036 commit_text = scm.commit_with_message("another test commit") 1037 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1038 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1039 1040 def test_commit_with_message_multiple_local_commits_and_working_copy(self): 1041 self._two_local_commits() 1042 write_into_file_at_path('test_file_commit1', 'working copy change') 1043 scm = detect_scm_system(self.git_checkout_path) 1044 1045 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit") 1046 commit_text = scm.commit_with_message("another test commit", force_squash=True) 1047 1048 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 1049 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1050 self.assertTrue(re.search(r'test_file_commit2', svn_log)) 1051 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1052 1053 def test_commit_with_message_git_commit_and_working_copy(self): 1054 self._two_local_commits() 1055 write_into_file_at_path('test_file_commit1', 'working copy change') 1056 scm = detect_scm_system(self.git_checkout_path) 1057 self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", git_commit="HEAD^") 1058 1059 def test_commit_with_message_multiple_local_commits_always_squash(self): 1060 self._two_local_commits() 1061 scm = detect_scm_system(self.git_checkout_path) 1062 scm._assert_can_squash = lambda working_directory_is_clean: True 1063 commit_text = scm.commit_with_message("yet another test commit") 1064 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 1065 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_multiple_local_commits(self): 1071 self._two_local_commits() 1072 scm = detect_scm_system(self.git_checkout_path) 1073 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit") 1074 commit_text = scm.commit_with_message("yet another test commit", force_squash=True) 1075 1076 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 1077 1078 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1079 self.assertTrue(re.search(r'test_file_commit2', svn_log)) 1080 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1081 1082 def test_commit_with_message_not_synced(self): 1083 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 1084 self._two_local_commits() 1085 scm = detect_scm_system(self.git_checkout_path) 1086 self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit") 1087 commit_text = scm.commit_with_message("another test commit", force_squash=True) 1088 1089 self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') 1090 1091 svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) 1092 self.assertFalse(re.search(r'test_file2', svn_log)) 1093 self.assertTrue(re.search(r'test_file_commit2', svn_log)) 1094 self.assertTrue(re.search(r'test_file_commit1', svn_log)) 1095 1096 def test_commit_with_message_not_synced_with_conflict(self): 1097 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 1098 self._local_commit('test_file2', 'asdf', 'asdf commit') 1099 1100 scm = detect_scm_system(self.git_checkout_path) 1101 # There's a conflict between trunk and the test_file2 modification. 1102 self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", force_squash=True) 1103 1104 def test_remote_branch_ref(self): 1105 self.assertEqual(self.scm.remote_branch_ref(), 'refs/remotes/trunk') 1106 1107 def test_reverse_diff(self): 1108 self._shared_test_reverse_diff() 1109 1110 def test_diff_for_revision(self): 1111 self._shared_test_diff_for_revision() 1112 1113 def test_svn_apply_git_patch(self): 1114 self._shared_test_svn_apply_git_patch() 1115 1116 def test_create_patch_local_plus_working_copy(self): 1117 self._one_local_commit_plus_working_copy_changes() 1118 scm = detect_scm_system(self.git_checkout_path) 1119 patch = scm.create_patch() 1120 self.assertTrue(re.search(r'test_file_commit1', patch)) 1121 self.assertTrue(re.search(r'test_file_commit2', patch)) 1122 1123 def test_create_patch(self): 1124 self._one_local_commit_plus_working_copy_changes() 1125 scm = detect_scm_system(self.git_checkout_path) 1126 patch = scm.create_patch() 1127 self.assertTrue(re.search(r'test_file_commit2', patch)) 1128 self.assertTrue(re.search(r'test_file_commit1', patch)) 1129 1130 def test_create_patch_with_changed_files(self): 1131 self._one_local_commit_plus_working_copy_changes() 1132 scm = detect_scm_system(self.git_checkout_path) 1133 patch = scm.create_patch(changed_files=['test_file_commit2']) 1134 self.assertTrue(re.search(r'test_file_commit2', patch)) 1135 1136 def test_create_patch_with_rm_and_changed_files(self): 1137 self._one_local_commit_plus_working_copy_changes() 1138 scm = detect_scm_system(self.git_checkout_path) 1139 os.remove('test_file_commit1') 1140 patch = scm.create_patch() 1141 patch_with_changed_files = scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2']) 1142 self.assertEquals(patch, patch_with_changed_files) 1143 1144 def test_create_patch_git_commit(self): 1145 self._two_local_commits() 1146 scm = detect_scm_system(self.git_checkout_path) 1147 patch = scm.create_patch(git_commit="HEAD^") 1148 self.assertTrue(re.search(r'test_file_commit1', patch)) 1149 self.assertFalse(re.search(r'test_file_commit2', patch)) 1150 1151 def test_create_patch_git_commit_range(self): 1152 self._three_local_commits() 1153 scm = detect_scm_system(self.git_checkout_path) 1154 patch = scm.create_patch(git_commit="HEAD~2..HEAD") 1155 self.assertFalse(re.search(r'test_file_commit0', patch)) 1156 self.assertTrue(re.search(r'test_file_commit2', patch)) 1157 self.assertTrue(re.search(r'test_file_commit1', patch)) 1158 1159 def test_create_patch_working_copy_only(self): 1160 self._one_local_commit_plus_working_copy_changes() 1161 scm = detect_scm_system(self.git_checkout_path) 1162 patch = scm.create_patch(git_commit="HEAD..") 1163 self.assertFalse(re.search(r'test_file_commit1', patch)) 1164 self.assertTrue(re.search(r'test_file_commit2', patch)) 1165 1166 def test_create_patch_multiple_local_commits(self): 1167 self._two_local_commits() 1168 scm = detect_scm_system(self.git_checkout_path) 1169 patch = scm.create_patch() 1170 self.assertTrue(re.search(r'test_file_commit2', patch)) 1171 self.assertTrue(re.search(r'test_file_commit1', patch)) 1172 1173 def test_create_patch_not_synced(self): 1174 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 1175 self._two_local_commits() 1176 scm = detect_scm_system(self.git_checkout_path) 1177 patch = scm.create_patch() 1178 self.assertFalse(re.search(r'test_file2', patch)) 1179 self.assertTrue(re.search(r'test_file_commit2', patch)) 1180 self.assertTrue(re.search(r'test_file_commit1', patch)) 1181 1182 def test_create_binary_patch(self): 1183 # Create a git binary patch and check the contents. 1184 scm = detect_scm_system(self.git_checkout_path) 1185 test_file_name = 'binary_file' 1186 test_file_path = os.path.join(self.git_checkout_path, test_file_name) 1187 file_contents = ''.join(map(chr, range(256))) 1188 write_into_file_at_path(test_file_path, file_contents, encoding=None) 1189 run_command(['git', 'add', test_file_name]) 1190 patch = scm.create_patch() 1191 self.assertTrue(re.search(r'\nliteral 0\n', patch)) 1192 self.assertTrue(re.search(r'\nliteral 256\n', patch)) 1193 1194 # Check if we can apply the created patch. 1195 run_command(['git', 'rm', '-f', test_file_name]) 1196 self._setup_webkittools_scripts_symlink(scm) 1197 self.checkout.apply_patch(self._create_patch(patch)) 1198 self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None)) 1199 1200 # Check if we can create a patch from a local commit. 1201 write_into_file_at_path(test_file_path, file_contents, encoding=None) 1202 run_command(['git', 'add', test_file_name]) 1203 run_command(['git', 'commit', '-m', 'binary diff']) 1204 patch_from_local_commit = scm.create_patch('HEAD') 1205 self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit)) 1206 self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit)) 1207 1208 def test_changed_files_local_plus_working_copy(self): 1209 self._one_local_commit_plus_working_copy_changes() 1210 scm = detect_scm_system(self.git_checkout_path) 1211 files = scm.changed_files() 1212 self.assertTrue('test_file_commit1' in files) 1213 self.assertTrue('test_file_commit2' in files) 1214 1215 def test_changed_files_git_commit(self): 1216 self._two_local_commits() 1217 scm = detect_scm_system(self.git_checkout_path) 1218 files = scm.changed_files(git_commit="HEAD^") 1219 self.assertTrue('test_file_commit1' in files) 1220 self.assertFalse('test_file_commit2' in files) 1221 1222 def test_changed_files_git_commit_range(self): 1223 self._three_local_commits() 1224 scm = detect_scm_system(self.git_checkout_path) 1225 files = scm.changed_files(git_commit="HEAD~2..HEAD") 1226 self.assertTrue('test_file_commit0' not in files) 1227 self.assertTrue('test_file_commit1' in files) 1228 self.assertTrue('test_file_commit2' in files) 1229 1230 def test_changed_files_working_copy_only(self): 1231 self._one_local_commit_plus_working_copy_changes() 1232 scm = detect_scm_system(self.git_checkout_path) 1233 files = scm.changed_files(git_commit="HEAD..") 1234 self.assertFalse('test_file_commit1' in files) 1235 self.assertTrue('test_file_commit2' in files) 1236 1237 def test_changed_files_multiple_local_commits(self): 1238 self._two_local_commits() 1239 scm = detect_scm_system(self.git_checkout_path) 1240 files = scm.changed_files() 1241 self.assertTrue('test_file_commit2' in files) 1242 self.assertTrue('test_file_commit1' in files) 1243 1244 def test_changed_files_not_synced(self): 1245 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 1246 self._two_local_commits() 1247 scm = detect_scm_system(self.git_checkout_path) 1248 files = scm.changed_files() 1249 self.assertFalse('test_file2' in files) 1250 self.assertTrue('test_file_commit2' in files) 1251 self.assertTrue('test_file_commit1' in files) 1252 1253 def test_changed_files_not_synced(self): 1254 run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 1255 self._two_local_commits() 1256 scm = detect_scm_system(self.git_checkout_path) 1257 files = scm.changed_files() 1258 self.assertFalse('test_file2' in files) 1259 self.assertTrue('test_file_commit2' in files) 1260 self.assertTrue('test_file_commit1' in files) 1261 1262 def test_changed_files(self): 1263 self._shared_test_changed_files() 1264 1265 def test_changed_files_for_revision(self): 1266 self._shared_test_changed_files_for_revision() 1267 1268 def test_contents_at_revision(self): 1269 self._shared_test_contents_at_revision() 1270 1271 def test_revisions_changing_file(self): 1272 self._shared_test_revisions_changing_file() 1273 1274 def test_added_files(self): 1275 self._shared_test_added_files() 1276 1277 def test_committer_email_for_revision(self): 1278 self._shared_test_committer_email_for_revision() 1279 1280 def test_add_recursively(self): 1281 self._shared_test_add_recursively() 1282 1283 def test_delete(self): 1284 self._two_local_commits() 1285 self.scm.delete('test_file_commit1') 1286 self.assertTrue("test_file_commit1" in self.scm.deleted_files()) 1287 1288 def test_to_object_name(self): 1289 relpath = 'test_file_commit1' 1290 fullpath = os.path.join(self.git_checkout_path, relpath) 1291 self._two_local_commits() 1292 self.assertEqual(relpath, self.scm.to_object_name(fullpath)) 1293 1294 def test_show_head(self): 1295 self._two_local_commits() 1296 self.assertEqual("more test content", self.scm.show_head('test_file_commit1')) 1297 1298 def test_show_head_binary(self): 1299 self._two_local_commits() 1300 data = "\244" 1301 write_into_file_at_path("binary_file", data, encoding=None) 1302 self.scm.add("binary_file") 1303 self.scm.commit_locally_with_message("a test commit") 1304 self.assertEqual(data, self.scm.show_head('binary_file')) 1305 1306 def test_diff_for_file(self): 1307 self._two_local_commits() 1308 write_into_file_at_path('test_file_commit1', "Updated", encoding=None) 1309 1310 diff = self.scm.diff_for_file('test_file_commit1') 1311 cached_diff = self.scm.diff_for_file('test_file_commit1') 1312 self.assertTrue("+Updated" in diff) 1313 self.assertTrue("-more test content" in diff) 1314 1315 self.scm.add('test_file_commit1') 1316 1317 cached_diff = self.scm.diff_for_file('test_file_commit1') 1318 self.assertTrue("+Updated" in cached_diff) 1319 self.assertTrue("-more test content" in cached_diff) 1320 1321 1322# We need to split off more of these SCM tests to use mocks instead of the filesystem. 1323# This class is the first part of that. 1324class GitTestWithMock(unittest.TestCase): 1325 def setUp(self): 1326 executive = MockExecutive(should_log=False) 1327 # We do this should_log dance to avoid logging when Git.__init__ runs sysctl on mac to check for 64-bit support. 1328 self.scm = Git(None, executive=executive) 1329 executive.should_log = True 1330 1331 def test_create_patch(self): 1332 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" 1333 OutputCapture().assert_outputs(self, self.scm.create_patch, kwargs={'changed_files': None}, expected_stderr=expected_stderr) 1334 1335 1336if __name__ == '__main__': 1337 unittest.main() 1338