Home | History | Annotate | Download | only in checkout
      1 # Copyright (C) 2009 Google Inc. All rights reserved.
      2 # Copyright (C) 2009 Apple Inc. All rights reserved.
      3 #
      4 # Redistribution and use in source and binary forms, with or without
      5 # modification, are permitted provided that the following conditions are
      6 # met:
      7 #
      8 #    * Redistributions of source code must retain the above copyright
      9 # notice, this list of conditions and the following disclaimer.
     10 #    * Redistributions in binary form must reproduce the above
     11 # copyright notice, this list of conditions and the following disclaimer
     12 # in the documentation and/or other materials provided with the
     13 # distribution.
     14 #    * Neither the name of Google Inc. nor the names of its
     15 # contributors may be used to endorse or promote products derived from
     16 # this software without specific prior written permission.
     17 #
     18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 from __future__ import with_statement
     31 
     32 import base64
     33 import codecs
     34 import getpass
     35 import os
     36 import os.path
     37 import re
     38 import stat
     39 import sys
     40 import subprocess
     41 import tempfile
     42 import unittest
     43 import urllib
     44 import shutil
     45 
     46 from datetime import date
     47 from webkitpy.common.checkout.api import Checkout
     48 from webkitpy.common.checkout.scm import detect_scm_system, SCM, SVN, Git, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError, AmbiguousCommitError, find_checkout_root, default_scm
     49 from webkitpy.common.config.committers import Committer  # FIXME: This should not be needed
     50 from webkitpy.common.net.bugzilla import Attachment # FIXME: This should not be needed
     51 from webkitpy.common.system.executive import Executive, run_command, ScriptError
     52 from webkitpy.common.system.outputcapture import OutputCapture
     53 from 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)
     60 def 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 
     69 def 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 
     78 def read_from_path(file_path, encoding="utf-8"):
     79     with codecs.open(file_path, "r", encoding) as file:
     80         return file.read()
     81 
     82 
     83 def _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 
     89 def _svn_diff(*args):
     90     return _make_diff("svn", *args)
     91 
     92 
     93 def _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
     98 class 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 
    181 class 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.
    232 class 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):
    259 svn: File or directory 'ChangeLog' is out of date; try updating
    260 svn: 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.
    277 class 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
    380 new file mode 100644
    381 index 0000000000000000000000000000000000000000..64a9532e7794fcd791f6f12157406d90
    382 60151690
    383 GIT binary patch
    384 literal 512
    385 zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c?
    386 zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap
    387 zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ
    388 zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A
    389 zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&)
    390 zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b
    391 zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB
    392 z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X
    393 z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4
    394 ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H
    395 
    396 literal 0
    397 HcmV?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
    410 index 64a9532e7794fcd791f6f12157406d9060151690..323fae03f4606ea9991df8befbb2fca7
    411 GIT binary patch
    412 literal 7
    413 OcmYex&reD$;sO8*F9L)B
    414 
    415 literal 512
    416 zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c?
    417 zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap
    418 zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ
    419 zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A
    420 zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&)
    421 zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b
    422 zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB
    423 z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X
    424 z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4
    425 ptUl-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
    437 deleted file mode 100644
    438 index 323fae0..0000000
    439 GIT binary patch
    440 literal 0
    441 HcmV?d00001
    442 
    443 literal 7
    444 OcmYex&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 
    461 class 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 (at] 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 (at] 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 (at] 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 (at] 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 (at] 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 (at] 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 (at] 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 ===================================================================
    608 Cannot display: file marked as a binary type.
    609 svn:mime-type = application/octet-stream
    610 
    611 Property changes on: test_file.swf
    612 ___________________________________________________________________
    613 Name: svn:mime-type
    614    + application/octet-stream
    615 
    616 
    617 Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
    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 (at] 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 
    782 class 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 
    830 class 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.
   1352 class 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 
   1364 if __name__ == '__main__':
   1365     unittest.main()
   1366