From 8ce0a13a85c249cd4667b0c3680457a5154ad4dd Mon Sep 17 00:00:00 2001 From: "maruel@chromium.org" Date: Thu, 12 Nov 2009 19:17:32 +0000 Subject: [PATCH] Split scm-specific functions out of gclient_scm.py to scm.py. TEST=unit tests fixed and reclassified BUG=none Review URL: http://codereview.chromium.org/389020 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@31809 0039d316-1c4b-4281-b951-d872f2087c98 --- PRESUBMIT.py | 2 + gclient_scm.py | 328 +------------------------------------ scm.py | 336 ++++++++++++++++++++++++++++++++++++++ tests/gclient_scm_test.py | 120 +------------- tests/gclient_test.py | 113 +------------ tests/scm_unittest.py | 279 +++++++++++++++++++++++++++++++ 6 files changed, 630 insertions(+), 548 deletions(-) create mode 100644 scm.py mode change 100644 => 100755 tests/gclient_scm_test.py create mode 100755 tests/scm_unittest.py diff --git a/PRESUBMIT.py b/PRESUBMIT.py index dffc680e5..813f56880 100755 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -13,8 +13,10 @@ UNIT_TESTS = [ 'tests.gcl_unittest', 'tests.gclient_test', 'tests.gclient_scm_test', + 'tests.gclient_utils_test', 'tests.presubmit_unittest', 'tests.revert_unittest', + 'tests.scm_unittest', 'tests.trychange_unittest', 'tests.watchlists_unittest', ] diff --git a/gclient_scm.py b/gclient_scm.py index e0c497644..e2459d91a 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Gclient-specific SCM-specific operations.""" import logging import os @@ -21,9 +22,10 @@ import sys import xml.dom.minidom import gclient_utils - -SVN_COMMAND = "svn" -GIT_COMMAND = "git" +# TODO(maruel): Temporary. +from scm import CaptureGit, CaptureGitStatus, CaptureSVN, +from scm import CaptureSVNHeadRevision, CaptureSVNInfo, CaptureSVNStatus, +from scm import RunSVN, RunSVNAndFilterOutput, RunSVNAndGetFileList ### SCM abstraction layer @@ -486,323 +488,3 @@ class SVNWrapper(SCMWrapper): filterer = DiffFilterer(self.relpath) RunSVNAndFilterOutput(command, path, False, False, filterer.Filter) - - -# ----------------------------------------------------------------------------- -# Git utils: - - -def CaptureGit(args, in_directory=None, print_error=True): - """Runs git, capturing output sent to stdout as a string. - - Args: - args: A sequence of command line parameters to be passed to git. - in_directory: The directory where git is to be run. - - Returns: - The output sent to stdout as a string. - """ - c = [GIT_COMMAND] - c.extend(args) - - # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for - # the git.exe executable, but shell=True makes subprocess on Linux fail - # when it's called with a list because it only tries to execute the - # first string ("git"). - stderr = None - if not print_error: - stderr = subprocess.PIPE - return subprocess.Popen(c, - cwd=in_directory, - shell=sys.platform.startswith('win'), - stdout=subprocess.PIPE, - stderr=stderr).communicate()[0] - - -def CaptureGitStatus(files, upstream_branch='origin'): - """Returns git status. - - @files can be a string (one file) or a list of files. - - Returns an array of (status, file) tuples.""" - command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] - if not files: - pass - elif isinstance(files, basestring): - command.append(files) - else: - command.extend(files) - - status = CaptureGit(command).rstrip() - results = [] - if status: - for statusline in status.split('\n'): - m = re.match('^(\w)\t(.+)$', statusline) - if not m: - raise Exception("status currently unsupported: %s" % statusline) - results.append(('%s ' % m.group(1), m.group(2))) - return results - - -# ----------------------------------------------------------------------------- -# SVN utils: - - -def RunSVN(args, in_directory): - """Runs svn, sending output to stdout. - - Args: - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - - Raises: - Error: An error occurred while running the svn command. - """ - c = [SVN_COMMAND] - c.extend(args) - - gclient_utils.SubprocessCall(c, in_directory) - - -def CaptureSVN(args, in_directory=None, print_error=True): - """Runs svn, capturing output sent to stdout as a string. - - Args: - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - - Returns: - The output sent to stdout as a string. - """ - c = [SVN_COMMAND] - c.extend(args) - - # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for - # the svn.exe executable, but shell=True makes subprocess on Linux fail - # when it's called with a list because it only tries to execute the - # first string ("svn"). - stderr = None - if not print_error: - stderr = subprocess.PIPE - return subprocess.Popen(c, - cwd=in_directory, - shell=(sys.platform == 'win32'), - stdout=subprocess.PIPE, - stderr=stderr).communicate()[0] - - -def RunSVNAndGetFileList(options, args, in_directory, file_list): - """Runs svn checkout, update, or status, output to stdout. - - The first item in args must be either "checkout", "update", or "status". - - svn's stdout is parsed to collect a list of files checked out or updated. - These files are appended to file_list. svn's stdout is also printed to - sys.stdout as in RunSVN. - - Args: - options: command line options to gclient - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - - Raises: - Error: An error occurred while running the svn command. - """ - command = [SVN_COMMAND] - command.extend(args) - - # svn update and svn checkout use the same pattern: the first three columns - # are for file status, property status, and lock status. This is followed - # by two spaces, and then the path to the file. - update_pattern = '^... (.*)$' - - # The first three columns of svn status are the same as for svn update and - # svn checkout. The next three columns indicate addition-with-history, - # switch, and remote lock status. This is followed by one space, and then - # the path to the file. - status_pattern = '^...... (.*)$' - - # args[0] must be a supported command. This will blow up if it's something - # else, which is good. Note that the patterns are only effective when - # these commands are used in their ordinary forms, the patterns are invalid - # for "svn status --show-updates", for example. - pattern = { - 'checkout': update_pattern, - 'status': status_pattern, - 'update': update_pattern, - }[args[0]] - - compiled_pattern = re.compile(pattern) - - def CaptureMatchingLines(line): - match = compiled_pattern.search(line) - if match: - file_list.append(match.group(1)) - - RunSVNAndFilterOutput(args, - in_directory, - options.verbose, - True, - CaptureMatchingLines) - -def RunSVNAndFilterOutput(args, - in_directory, - print_messages, - print_stdout, - filter): - """Runs svn checkout, update, status, or diff, optionally outputting - to stdout. - - The first item in args must be either "checkout", "update", - "status", or "diff". - - svn's stdout is passed line-by-line to the given filter function. If - print_stdout is true, it is also printed to sys.stdout as in RunSVN. - - Args: - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - print_messages: Whether to print status messages to stdout about - which Subversion commands are being run. - print_stdout: Whether to forward Subversion's output to stdout. - filter: A function taking one argument (a string) which will be - passed each line (with the ending newline character removed) of - Subversion's output for filtering. - - Raises: - Error: An error occurred while running the svn command. - """ - command = [SVN_COMMAND] - command.extend(args) - - gclient_utils.SubprocessCallAndFilter(command, - in_directory, - print_messages, - print_stdout, - filter=filter) - -def CaptureSVNInfo(relpath, in_directory=None, print_error=True): - """Returns a dictionary from the svn info output for the given file. - - Args: - relpath: The directory where the working copy resides relative to - the directory given by in_directory. - in_directory: The directory where svn is to be run. - """ - output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error) - dom = gclient_utils.ParseXML(output) - result = {} - if dom: - GetNamedNodeText = gclient_utils.GetNamedNodeText - GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText - def C(item, f): - if item is not None: return f(item) - # /info/entry/ - # url - # reposityory/(root|uuid) - # wc-info/(schedule|depth) - # commit/(author|date) - # str() the results because they may be returned as Unicode, which - # interferes with the higher layers matching up things in the deps - # dictionary. - # TODO(maruel): Fix at higher level instead (!) - result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) - result['URL'] = C(GetNamedNodeText(dom, 'url'), str) - result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) - result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', 'revision'), - int) - result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), - str) - result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) - result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) - result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) - result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) - return result - - -def CaptureSVNHeadRevision(url): - """Get the head revision of a SVN repository. - - Returns: - Int head revision - """ - info = CaptureSVN(["info", "--xml", url], os.getcwd()) - dom = xml.dom.minidom.parseString(info) - return dom.getElementsByTagName('entry')[0].getAttribute('revision') - - -def CaptureSVNStatus(files): - """Returns the svn 1.5 svn status emulated output. - - @files can be a string (one file) or a list of files. - - Returns an array of (status, file) tuples.""" - command = ["status", "--xml"] - if not files: - pass - elif isinstance(files, basestring): - command.append(files) - else: - command.extend(files) - - status_letter = { - None: ' ', - '': ' ', - 'added': 'A', - 'conflicted': 'C', - 'deleted': 'D', - 'external': 'X', - 'ignored': 'I', - 'incomplete': '!', - 'merged': 'G', - 'missing': '!', - 'modified': 'M', - 'none': ' ', - 'normal': ' ', - 'obstructed': '~', - 'replaced': 'R', - 'unversioned': '?', - } - dom = gclient_utils.ParseXML(CaptureSVN(command)) - results = [] - if dom: - # /status/target/entry/(wc-status|commit|author|date) - for target in dom.getElementsByTagName('target'): - for entry in target.getElementsByTagName('entry'): - file_path = entry.getAttribute('path') - wc_status = entry.getElementsByTagName('wc-status') - assert len(wc_status) == 1 - # Emulate svn 1.5 status ouput... - statuses = [' '] * 7 - # Col 0 - xml_item_status = wc_status[0].getAttribute('item') - if xml_item_status in status_letter: - statuses[0] = status_letter[xml_item_status] - else: - raise Exception('Unknown item status "%s"; please implement me!' % - xml_item_status) - # Col 1 - xml_props_status = wc_status[0].getAttribute('props') - if xml_props_status == 'modified': - statuses[1] = 'M' - elif xml_props_status == 'conflicted': - statuses[1] = 'C' - elif (not xml_props_status or xml_props_status == 'none' or - xml_props_status == 'normal'): - pass - else: - raise Exception('Unknown props status "%s"; please implement me!' % - xml_props_status) - # Col 2 - if wc_status[0].getAttribute('wc-locked') == 'true': - statuses[2] = 'L' - # Col 3 - if wc_status[0].getAttribute('copied') == 'true': - statuses[3] = '+' - # Col 4 - if wc_status[0].getAttribute('switched') == 'true': - statuses[4] = 'S' - # TODO(maruel): Col 5 and 6 - item = (''.join(statuses), file_path) - results.append(item) - return results diff --git a/scm.py b/scm.py new file mode 100644 index 000000000..12848152a --- /dev/null +++ b/scm.py @@ -0,0 +1,336 @@ +# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""SCM-specific functions.""" + +import os +import re +import subprocess +import sys +import xml.dom.minidom + +import gclient_utils + + +SVN_COMMAND = "svn" +GIT_COMMAND = "git" + +# ----------------------------------------------------------------------------- +# Git utils: + + +def CaptureGit(args, in_directory=None, print_error=True): + """Runs git, capturing output sent to stdout as a string. + + Args: + args: A sequence of command line parameters to be passed to git. + in_directory: The directory where git is to be run. + + Returns: + The output sent to stdout as a string. + """ + c = [GIT_COMMAND] + c.extend(args) + + # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for + # the git.exe executable, but shell=True makes subprocess on Linux fail + # when it's called with a list because it only tries to execute the + # first string ("git"). + stderr = None + if not print_error: + stderr = subprocess.PIPE + return subprocess.Popen(c, + cwd=in_directory, + shell=sys.platform.startswith('win'), + stdout=subprocess.PIPE, + stderr=stderr).communicate()[0] + + +def CaptureGitStatus(files, upstream_branch='origin'): + """Returns git status. + + @files can be a string (one file) or a list of files. + + Returns an array of (status, file) tuples.""" + command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] + if not files: + pass + elif isinstance(files, basestring): + command.append(files) + else: + command.extend(files) + + status = CaptureGit(command).rstrip() + results = [] + if status: + for statusline in status.split('\n'): + m = re.match('^(\w)\t(.+)$', statusline) + if not m: + raise Exception("status currently unsupported: %s" % statusline) + results.append(('%s ' % m.group(1), m.group(2))) + return results + + +# ----------------------------------------------------------------------------- +# SVN utils: + + +def RunSVN(args, in_directory): + """Runs svn, sending output to stdout. + + Args: + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + + Raises: + Error: An error occurred while running the svn command. + """ + c = [SVN_COMMAND] + c.extend(args) + + gclient_utils.SubprocessCall(c, in_directory) + + +def CaptureSVN(args, in_directory=None, print_error=True): + """Runs svn, capturing output sent to stdout as a string. + + Args: + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + + Returns: + The output sent to stdout as a string. + """ + c = [SVN_COMMAND] + c.extend(args) + + # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for + # the svn.exe executable, but shell=True makes subprocess on Linux fail + # when it's called with a list because it only tries to execute the + # first string ("svn"). + stderr = None + if not print_error: + stderr = subprocess.PIPE + return subprocess.Popen(c, + cwd=in_directory, + shell=(sys.platform == 'win32'), + stdout=subprocess.PIPE, + stderr=stderr).communicate()[0] + + +def RunSVNAndGetFileList(options, args, in_directory, file_list): + """Runs svn checkout, update, or status, output to stdout. + + The first item in args must be either "checkout", "update", or "status". + + svn's stdout is parsed to collect a list of files checked out or updated. + These files are appended to file_list. svn's stdout is also printed to + sys.stdout as in RunSVN. + + Args: + options: command line options to gclient + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + + Raises: + Error: An error occurred while running the svn command. + """ + command = [SVN_COMMAND] + command.extend(args) + + # svn update and svn checkout use the same pattern: the first three columns + # are for file status, property status, and lock status. This is followed + # by two spaces, and then the path to the file. + update_pattern = '^... (.*)$' + + # The first three columns of svn status are the same as for svn update and + # svn checkout. The next three columns indicate addition-with-history, + # switch, and remote lock status. This is followed by one space, and then + # the path to the file. + status_pattern = '^...... (.*)$' + + # args[0] must be a supported command. This will blow up if it's something + # else, which is good. Note that the patterns are only effective when + # these commands are used in their ordinary forms, the patterns are invalid + # for "svn status --show-updates", for example. + pattern = { + 'checkout': update_pattern, + 'status': status_pattern, + 'update': update_pattern, + }[args[0]] + + compiled_pattern = re.compile(pattern) + + def CaptureMatchingLines(line): + match = compiled_pattern.search(line) + if match: + file_list.append(match.group(1)) + + RunSVNAndFilterOutput(args, + in_directory, + options.verbose, + True, + CaptureMatchingLines) + +def RunSVNAndFilterOutput(args, + in_directory, + print_messages, + print_stdout, + filter): + """Runs svn checkout, update, status, or diff, optionally outputting + to stdout. + + The first item in args must be either "checkout", "update", + "status", or "diff". + + svn's stdout is passed line-by-line to the given filter function. If + print_stdout is true, it is also printed to sys.stdout as in RunSVN. + + Args: + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + print_messages: Whether to print status messages to stdout about + which Subversion commands are being run. + print_stdout: Whether to forward Subversion's output to stdout. + filter: A function taking one argument (a string) which will be + passed each line (with the ending newline character removed) of + Subversion's output for filtering. + + Raises: + Error: An error occurred while running the svn command. + """ + command = [SVN_COMMAND] + command.extend(args) + + gclient_utils.SubprocessCallAndFilter(command, + in_directory, + print_messages, + print_stdout, + filter=filter) + +def CaptureSVNInfo(relpath, in_directory=None, print_error=True): + """Returns a dictionary from the svn info output for the given file. + + Args: + relpath: The directory where the working copy resides relative to + the directory given by in_directory. + in_directory: The directory where svn is to be run. + """ + output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error) + dom = gclient_utils.ParseXML(output) + result = {} + if dom: + GetNamedNodeText = gclient_utils.GetNamedNodeText + GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText + def C(item, f): + if item is not None: return f(item) + # /info/entry/ + # url + # reposityory/(root|uuid) + # wc-info/(schedule|depth) + # commit/(author|date) + # str() the results because they may be returned as Unicode, which + # interferes with the higher layers matching up things in the deps + # dictionary. + # TODO(maruel): Fix at higher level instead (!) + result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) + result['URL'] = C(GetNamedNodeText(dom, 'url'), str) + result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) + result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', 'revision'), + int) + result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), + str) + result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) + result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) + result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) + result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) + return result + + +def CaptureSVNHeadRevision(url): + """Get the head revision of a SVN repository. + + Returns: + Int head revision + """ + info = CaptureSVN(["info", "--xml", url], os.getcwd()) + dom = xml.dom.minidom.parseString(info) + return dom.getElementsByTagName('entry')[0].getAttribute('revision') + + +def CaptureSVNStatus(files): + """Returns the svn 1.5 svn status emulated output. + + @files can be a string (one file) or a list of files. + + Returns an array of (status, file) tuples.""" + command = ["status", "--xml"] + if not files: + pass + elif isinstance(files, basestring): + command.append(files) + else: + command.extend(files) + + status_letter = { + None: ' ', + '': ' ', + 'added': 'A', + 'conflicted': 'C', + 'deleted': 'D', + 'external': 'X', + 'ignored': 'I', + 'incomplete': '!', + 'merged': 'G', + 'missing': '!', + 'modified': 'M', + 'none': ' ', + 'normal': ' ', + 'obstructed': '~', + 'replaced': 'R', + 'unversioned': '?', + } + dom = gclient_utils.ParseXML(CaptureSVN(command)) + results = [] + if dom: + # /status/target/entry/(wc-status|commit|author|date) + for target in dom.getElementsByTagName('target'): + for entry in target.getElementsByTagName('entry'): + file_path = entry.getAttribute('path') + wc_status = entry.getElementsByTagName('wc-status') + assert len(wc_status) == 1 + # Emulate svn 1.5 status ouput... + statuses = [' '] * 7 + # Col 0 + xml_item_status = wc_status[0].getAttribute('item') + if xml_item_status in status_letter: + statuses[0] = status_letter[xml_item_status] + else: + raise Exception('Unknown item status "%s"; please implement me!' % + xml_item_status) + # Col 1 + xml_props_status = wc_status[0].getAttribute('props') + if xml_props_status == 'modified': + statuses[1] = 'M' + elif xml_props_status == 'conflicted': + statuses[1] = 'C' + elif (not xml_props_status or xml_props_status == 'none' or + xml_props_status == 'normal'): + pass + else: + raise Exception('Unknown props status "%s"; please implement me!' % + xml_props_status) + # Col 2 + if wc_status[0].getAttribute('wc-locked') == 'true': + statuses[2] = 'L' + # Col 3 + if wc_status[0].getAttribute('copied') == 'true': + statuses[3] = '+' + # Col 4 + if wc_status[0].getAttribute('switched') == 'true': + statuses[4] = 'S' + # TODO(maruel): Col 5 and 6 + item = (''.join(statuses), file_path) + results.append(item) + return results diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py old mode 100644 new mode 100755 index e61101073..11a2cd933 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -267,115 +267,6 @@ class SVNWrapperTestCase(BaseTestCase): file_list = [] scm.update(options, self.args, file_list) - def testGetSVNFileInfo(self): - xml_text = r""" - - -http://src.chromium.org/svn/trunk/src/chrome/app/d -http://src.chromium.org/svn - -add -infinity -http://src.chromium.org/svn/trunk/src/chrome/app/DEPS -14628 -369f59057ba0e6d9017e28f8bdfb1f43 - - - -""" % self.url - gclient_scm.CaptureSVN(['info', '--xml', self.url], - '.', True).AndReturn(xml_text) - expected = { - 'URL': 'http://src.chromium.org/svn/trunk/src/chrome/app/d', - 'UUID': None, - 'Repository Root': 'http://src.chromium.org/svn', - 'Schedule': 'add', - 'Copied From URL': - 'http://src.chromium.org/svn/trunk/src/chrome/app/DEPS', - 'Copied From Rev': '14628', - 'Path': self.url, - 'Revision': 14628, - 'Node Kind': 'file', - } - self.mox.ReplayAll() - file_info = self._CaptureSVNInfo(self.url, '.', True) - self.assertEquals(sorted(file_info.items()), sorted(expected.items())) - - def testCaptureSvnInfo(self): - xml_text = """ - - -%s - -%s -7b9385f5-0452-0410-af26-ad4892b7a1fb - - -normal -infinity - - -maruel -2008-12-04T20:12:19.685120Z - - - -""" % (self.url, self.root_dir) - gclient_scm.CaptureSVN(['info', '--xml', - self.url], '.', True).AndReturn(xml_text) - self.mox.ReplayAll() - file_info = self._CaptureSVNInfo(self.url, '.', True) - expected = { - 'URL': self.url, - 'UUID': '7b9385f5-0452-0410-af26-ad4892b7a1fb', - 'Revision': 35, - 'Repository Root': self.root_dir, - 'Schedule': 'normal', - 'Copied From URL': None, - 'Copied From Rev': None, - 'Path': '.', - 'Node Kind': 'dir', - } - self.assertEqual(file_info, expected) - - def testRevinfo(self): - options = self.Options(verbose=False) - xml_text = """ - - -%s - -%s -7b9385f5-0452-0410-af26-ad4892b7a1fb - - -normal -infinity - - -maruel -2008-12-04T20:12:19.685120Z - - - -""" % (self.url, self.root_dir) - gclient_scm.os.getcwd().AndReturn('bleh') - gclient_scm.CaptureSVN(['info', '--xml', self.url], 'bleh' - ).AndReturn(xml_text) - self.mox.ReplayAll() - scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, - relpath=self.relpath) - rev_info = scm.revinfo(options, self.args, None) - self.assertEqual(rev_info, '35') - class GitWrapperTestCase(SuperMoxBaseTestBase): """This class doesn't use pymox.""" @@ -437,6 +328,7 @@ from :3 return self.OptionsObject(self, *args, **kwargs) def CreateGitRepo(self, git_import, path): + """Do it for real.""" try: Popen(['git', 'init'], stdout=PIPE, stderr=STDOUT, cwd=path).communicate() @@ -612,16 +504,6 @@ from :3 self.assertEquals(rev_info, '069c602044c5388d2d15c3f875b057c852003458') -class RunSVNTestCase(BaseTestCase): - def testRunSVN(self): - self.UnMock(gclient_scm, 'RunSVN') - param2 = 'bleh' - gclient_scm.gclient_utils.SubprocessCall(['svn', 'foo', 'bar'], - param2).AndReturn(None) - self.mox.ReplayAll() - gclient_scm.RunSVN(['foo', 'bar'], param2) - - if __name__ == '__main__': import unittest unittest.main() diff --git a/tests/gclient_test.py b/tests/gclient_test.py index d335d4266..dc3dff4a5 100755 --- a/tests/gclient_test.py +++ b/tests/gclient_test.py @@ -22,6 +22,8 @@ import __builtin__ import StringIO import gclient +# Temporary due to the "from scm import *" in gclient_scm. +import scm from super_mox import mox, IsOneOf, SuperMoxTestBase @@ -53,6 +55,11 @@ class GClientBaseTestCase(BaseTestCase): self.mox.StubOutWithMock(gclient.gclient_scm, 'CaptureSVNStatus') self.mox.StubOutWithMock(gclient.gclient_scm, 'RunSVN') self.mox.StubOutWithMock(gclient.gclient_scm, 'RunSVNAndGetFileList') + self.mox.StubOutWithMock(scm, 'CaptureSVN') + self.mox.StubOutWithMock(scm, 'CaptureSVNInfo') + self.mox.StubOutWithMock(scm, 'CaptureSVNStatus') + self.mox.StubOutWithMock(scm, 'RunSVN') + self.mox.StubOutWithMock(scm, 'RunSVNAndGetFileList') self._gclient_gclient = gclient.GClient gclient.GClient = self.mox.CreateMockAnything() self._scm_wrapper = gclient.gclient_scm.CreateSCM @@ -1066,112 +1073,6 @@ deps = { pass -class SubprocessCallAndFilterTestCase(BaseTestCase): - def setUp(self): - BaseTestCase.setUp(self) - self.mox.StubOutWithMock(gclient.gclient_scm, 'CaptureSVN') - - def testSubprocessCallAndFilter(self): - command = ['boo', 'foo', 'bar'] - in_directory = 'bleh' - fail_status = None - pattern = 'a(.*)b' - test_string = 'ahah\naccb\nallo\naddb\n' - class Mock(object): - stdout = StringIO.StringIO(test_string) - def wait(self): - pass - kid = Mock() - print("\n________ running 'boo foo bar' in 'bleh'") - for i in test_string: - gclient.sys.stdout.write(i) - gclient.gclient_utils.subprocess.Popen( - command, bufsize=0, cwd=in_directory, - shell=(gclient.sys.platform == 'win32'), - stdout=gclient.gclient_utils.subprocess.PIPE, - stderr=gclient.gclient_utils.subprocess.STDOUT).AndReturn(kid) - self.mox.ReplayAll() - compiled_pattern = gclient.re.compile(pattern) - line_list = [] - capture_list = [] - def FilterLines(line): - line_list.append(line) - match = compiled_pattern.search(line) - if match: - capture_list.append(match.group(1)) - gclient.gclient_utils.SubprocessCallAndFilter( - command, in_directory, - True, True, - fail_status, FilterLines) - self.assertEquals(line_list, ['ahah', 'accb', 'allo', 'addb']) - self.assertEquals(capture_list, ['cc', 'dd']) - - def testCaptureSVNStatus(self): - gclient.gclient_scm.CaptureSVN( - ['status', '--xml', '.'] - ).AndReturn(r""" - - - - - - - - -ajwong@chromium.org -2009-04-16T00:42:06.872358Z - - - - - - - - - - -brettw@google.com -2008-08-23T17:16:42.090152Z - - - - - - -nsylvain@chromium.org -2009-04-27T19:37:17.977400Z - - - - - -""") - self.mox.ReplayAll() - info = gclient.gclient_scm.CaptureSVNStatus('.') - expected = [ - ('? ', 'unversionned_file.txt'), - ('M ', 'build\\internal\\essential.vsprops'), - ('A + ', 'chrome\\app\\d'), - ('MM ', 'chrome\\app\\DEPS'), - ('C ', 'scripts\\master\\factory\\gclient_factory.py'), - ] - self.assertEquals(sorted(info), sorted(expected)) - - def testCaptureSVNStatusEmpty(self): - gclient.gclient_scm.CaptureSVN( - ['status', '--xml'] - ).AndReturn(r""" - - - - -""") - self.mox.ReplayAll() - info = gclient.gclient_scm.CaptureSVNStatus(None) - self.assertEquals(info, []) - - if __name__ == '__main__': import unittest unittest.main() diff --git a/tests/scm_unittest.py b/tests/scm_unittest.py new file mode 100755 index 000000000..68846d71e --- /dev/null +++ b/tests/scm_unittest.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for scm.py.""" + +from gclient_test import BaseTestCase +import scm +from super_mox import mox + + +class BaseSCMTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.setUp(self) + self.mox.StubOutWithMock(scm.gclient_utils, 'SubprocessCall') + self.mox.StubOutWithMock(scm.gclient_utils, 'SubprocessCallAndFilter') + + +class RootTestCase(BaseSCMTestCase): + def testMembersChanged(self): + self.mox.ReplayAll() + members = [ + 'CaptureGit', 'CaptureGitStatus', 'GIT_COMMAND', + 'CaptureSVN', 'CaptureSVNHeadRevision', 'CaptureSVNInfo', + 'CaptureSVNStatus', 'RunSVN', 'RunSVNAndFilterOutput', + 'RunSVNAndGetFileList', 'SVN_COMMAND', + 'gclient_utils', 'os', 're', 'subprocess', 'sys', 'xml', + ] + # If this test fails, you should add the relevant test. + self.compareMembers(scm, members) + + +class GitWrapperTestCase(BaseSCMTestCase): + sample_git_import = """blob +mark :1 +data 6 +Hello + +blob +mark :2 +data 4 +Bye + +reset refs/heads/master +commit refs/heads/master +mark :3 +author Bob 1253744361 -0700 +committer Bob 1253744361 -0700 +data 8 +A and B +M 100644 :1 a +M 100644 :2 b + +blob +mark :4 +data 10 +Hello +You + +blob +mark :5 +data 8 +Bye +You + +commit refs/heads/origin +mark :6 +author Alice 1253744424 -0700 +committer Alice 1253744424 -0700 +data 13 +Personalized +from :3 +M 100644 :4 a +M 100644 :5 b + +reset refs/heads/master +from :3 +""" + + def CreateGitRepo(self, git_import, path): + try: + subprocess.Popen(['git', 'init'], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=path).communicate() + except WindowsError: + # git is not available, skip this test. + return False + subprocess.Popen(['git', 'fast-import'], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + cwd=path).communicate(input=git_import) + subprocess.Popen(['git', 'checkout'], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=path).communicate() + return True + + def setUp(self): + BaseSCMTestCase.setUp(self) + self.args = self.Args() + self.url = 'git://foo' + self.root_dir = tempfile.mkdtemp() + self.relpath = '.' + self.base_path = os.path.join(self.root_dir, self.relpath) + self.enabled = self.CreateGitRepo(self.sample_git_import, self.base_path) + + def tearDown(self): + shutil.rmtree(self.root_dir) + gclient_test.BaseTestCase.tearDown(self) + + +class SVNTestCase(BaseSCMTestCase): + def setUp(self): + BaseSCMTestCase.setUp(self) + self.root_dir = self.Dir() + self.args = self.Args() + self.url = self.Url() + self.relpath = 'asf' + + def testGetSVNFileInfo(self): + xml_text = r""" + + +http://src.chromium.org/svn/trunk/src/chrome/app/d +http://src.chromium.org/svn + +add +infinity +http://src.chromium.org/svn/trunk/src/chrome/app/DEPS +14628 +369f59057ba0e6d9017e28f8bdfb1f43 + + + +""" % self.url + self.mox.StubOutWithMock(scm, 'CaptureSVN') + scm.CaptureSVN(['info', '--xml', self.url], '.', True).AndReturn(xml_text) + expected = { + 'URL': 'http://src.chromium.org/svn/trunk/src/chrome/app/d', + 'UUID': None, + 'Repository Root': 'http://src.chromium.org/svn', + 'Schedule': 'add', + 'Copied From URL': + 'http://src.chromium.org/svn/trunk/src/chrome/app/DEPS', + 'Copied From Rev': '14628', + 'Path': self.url, + 'Revision': 14628, + 'Node Kind': 'file', + } + self.mox.ReplayAll() + file_info = scm.CaptureSVNInfo(self.url, '.', True) + self.assertEquals(sorted(file_info.items()), sorted(expected.items())) + + def testCaptureSvnInfo(self): + xml_text = """ + + +%s + +%s +7b9385f5-0452-0410-af26-ad4892b7a1fb + + +normal +infinity + + +maruel +2008-12-04T20:12:19.685120Z + + + +""" % (self.url, self.root_dir) + self.mox.StubOutWithMock(scm, 'CaptureSVN') + scm.CaptureSVN(['info', '--xml', self.url], '.', True).AndReturn(xml_text) + self.mox.ReplayAll() + file_info = scm.CaptureSVNInfo(self.url, '.', True) + expected = { + 'URL': self.url, + 'UUID': '7b9385f5-0452-0410-af26-ad4892b7a1fb', + 'Revision': 35, + 'Repository Root': self.root_dir, + 'Schedule': 'normal', + 'Copied From URL': None, + 'Copied From Rev': None, + 'Path': '.', + 'Node Kind': 'dir', + } + self.assertEqual(file_info, expected) + + def testCaptureSVNStatus(self): + text =r""" + + + + + + + + +ajwong@chromium.org +2009-04-16T00:42:06.872358Z + + + + + + + + + + +brettw@google.com +2008-08-23T17:16:42.090152Z + + + + + + +nsylvain@chromium.org +2009-04-27T19:37:17.977400Z + + + + + +""" + proc = self.mox.CreateMockAnything() + scm.subprocess.Popen(['svn', 'status', '--xml', '.'], + cwd=None, + shell=scm.sys.platform.startswith('win'), + stderr=None, + stdout=scm.subprocess.PIPE).AndReturn(proc) + proc.communicate().AndReturn((text, 0)) + + self.mox.ReplayAll() + info = scm.CaptureSVNStatus('.') + expected = [ + ('? ', 'unversionned_file.txt'), + ('M ', 'build\\internal\\essential.vsprops'), + ('A + ', 'chrome\\app\\d'), + ('MM ', 'chrome\\app\\DEPS'), + ('C ', 'scripts\\master\\factory\\gclient_factory.py'), + ] + self.assertEquals(sorted(info), sorted(expected)) + + def testRunSVN(self): + param2 = 'bleh' + scm.gclient_utils.SubprocessCall(['svn', 'foo', 'bar'], + param2).AndReturn(None) + self.mox.ReplayAll() + scm.RunSVN(['foo', 'bar'], param2) + + def testCaptureSVNStatusEmpty(self): + text = r""" + + + + """ + proc = self.mox.CreateMockAnything() + scm.subprocess.Popen(['svn', 'status', '--xml'], + cwd=None, + shell=scm.sys.platform.startswith('win'), + stderr=None, + stdout=scm.subprocess.PIPE).AndReturn(proc) + proc.communicate().AndReturn((text, 0)) + self.mox.ReplayAll() + info = scm.CaptureSVNStatus(None) + self.assertEquals(info, []) + + +if __name__ == '__main__': + import unittest + unittest.main() + +# vim: ts=2:sw=2:tw=80:et: