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