Reapply 48271 with fix.

"""
Enable automatic command and one-liner doc. Reformat pydoc accordingly.
Add parser as an argument and parse_args hook in preparation to move parse_args at the right place, inside the CMDxx functions.

R.I.P. gclient_test.py
"""
original code review at: http://codereview.chromium.org/2253002

In addition:
- Add more smoke tests: vars, hooks, runhooks
- Bugs filed about misbehavior with git checkout
- Fixed gclient runhooks

TEST=smoke test
BUG=23328

Review URL: http://codereview.chromium.org/2265002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@48289 0039d316-1c4b-4281-b951-d872f2087c98
experimental/szager/collated-output
maruel@chromium.org 15 years ago
parent 5434b4e20a
commit 3365c913c4

@ -10,7 +10,6 @@ details on the presubmit API built into gcl.
UNIT_TESTS = [ UNIT_TESTS = [
'tests.gcl_unittest', 'tests.gcl_unittest',
'tests.gclient_test',
'tests.gclient_scm_test', 'tests.gclient_scm_test',
'tests.gclient_utils_test', 'tests.gclient_utils_test',
'tests.presubmit_unittest', 'tests.presubmit_unittest',

@ -55,7 +55,7 @@ Hooks
] ]
""" """
__version__ = "0.3.7" __version__ = "0.4"
import errno import errno
import logging import logging
@ -73,22 +73,6 @@ import gclient_scm
import gclient_utils import gclient_utils
from third_party.repo.progress import Progress from third_party.repo.progress import Progress
# default help text
DEFAULT_USAGE_TEXT = (
"""%prog <subcommand> [options] [--] [SCM options/args...]
a wrapper for managing a set of svn client modules and/or git repositories.
Version """ + __version__ + """
Options and extra arguments can be passed to invoked SCM commands by
appending them to the command line. Note that if the first such
appended option starts with a dash (-) then the options must be
preceded by -- to distinguish them from gclient options.
For additional help on a subcommand or examples of usage, try
%prog help <subcommand>
%prog help files
""")
def attr(attr, data): def attr(attr, data):
"""Sets an attribute on a function.""" """Sets an attribute on a function."""
@ -470,6 +454,8 @@ solutions = [
def _RunHookAction(self, hook_dict, matching_file_list): def _RunHookAction(self, hook_dict, matching_file_list):
"""Runs the action from a single hook. """Runs the action from a single hook.
""" """
logging.info(hook_dict)
logging.info(matching_file_list)
command = hook_dict['action'][:] command = hook_dict['action'][:]
if command[0] == 'python': if command[0] == 'python':
# If the hook specified "python" as the first item, the action is a # If the hook specified "python" as the first item, the action is a
@ -813,19 +799,15 @@ solutions = [
print(snapclient._config_content) print(snapclient._config_content)
## gclient commands. #### gclient commands.
def CMDcleanup(parser, options, args):
"""Clean up all working copies, using 'svn cleanup' for each module.
Additional options and args may be passed to 'svn cleanup'. def CMDcleanup(parser, args):
"""Cleans up all working copies.
usage: cleanup [options] [--] [svn cleanup args/options] Mostly svn-specific. Simply runs 'svn cleanup' for each module.
Valid options:
--verbose : output additional diagnostics
""" """
(options, args) = parser.parse_args(args)
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
@ -836,32 +818,23 @@ Valid options:
return client.RunOnDeps('cleanup', args) return client.RunOnDeps('cleanup', args)
def CMDconfig(parser, options, args): @attr('usage', '[url] [safesync url]')
def CMDconfig(parser, args):
"""Create a .gclient file in the current directory. """Create a .gclient file in the current directory.
This specifies the configuration for further commands. After update/sync, This specifies the configuration for further commands. After update/sync,
top-level DEPS files in each module are read to determine dependent top-level DEPS files in each module are read to determine dependent
modules to operate on as well. If optional [url] parameter is modules to operate on as well. If optional [url] parameter is
provided, then configuration is read from a specified Subversion server provided, then configuration is read from a specified Subversion server
URL. Otherwise, a --spec option must be provided. A --name option overrides URL.
the default name for the solutions.
usage: config [option | url] [safesync url]
Valid options:
--name path : alternate relative path for the solution
--spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
*Note that due to Cygwin/Python brokenness, it
probably can't contain any newlines.*
Examples:
gclient config https://gclient.googlecode.com/svn/trunk/gclient
configure a new client to check out gclient.py tool sources
gclient config --name tools https://gclient.googlecode.com/svn/trunk/gclient
gclient config --spec='solutions=[{"name":"gclient",
'"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
'"custom_deps":{}}]'
""" """
parser.add_option("--spec",
help="create a gclient file containing the provided "
"string. Due to Cygwin/Python brokenness, it "
"probably can't contain any newlines.")
parser.add_option("--name",
help="overrides the default name for the solution")
(options, args) = parser.parse_args(args)
if len(args) < 1 and not options.spec: if len(args) < 1 and not options.spec:
raise gclient_utils.Error("required argument missing; see 'gclient help " raise gclient_utils.Error("required argument missing; see 'gclient help "
"config'") "config'")
@ -886,8 +859,9 @@ Examples:
return 0 return 0
def CMDexport(parser, options, args): def CMDexport(parser, args):
"""Wrapper for svn export for all managed directories.""" """Wrapper for svn export for all managed directories."""
(options, args) = parser.parse_args(args)
if len(args) != 1: if len(args) != 1:
raise gclient_utils.Error("Need directory name") raise gclient_utils.Error("Need directory name")
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
@ -902,30 +876,19 @@ def CMDexport(parser, options, args):
return client.RunOnDeps('export', args) return client.RunOnDeps('export', args)
def CMDpack(parser, options, args): @attr('epilog', """Example:
gclient pack > patch.txt
generate simple patch for configured client and dependences
""")
def CMDpack(parser, args):
"""Generate a patch which can be applied at the root of the tree. """Generate a patch which can be applied at the root of the tree.
Internally, runs 'svn diff' on each checked out module and Internally, runs 'svn diff'/'git diff' on each checked out module and
dependencies, and performs minimal postprocessing of the output. The dependencies, and performs minimal postprocessing of the output. The
resulting patch is printed to stdout and can be applied to a freshly resulting patch is printed to stdout and can be applied to a freshly
checked out tree via 'patch -p0 < patchfile'. Additional args and checked out tree via 'patch -p0 < patchfile'.
options to 'svn diff' can be passed after gclient options.
usage: pack [options] [--] [svn args/options]
Valid options:
--verbose : output additional diagnostics
Examples:
gclient pack > patch.txt
generate simple patch for configured client and dependences
gclient pack -- -x -b > patch.txt
generate patch using 'svn diff -x -b' to suppress
whitespace-only differences
gclient pack -- -r HEAD -x -b > patch.txt
generate patch, diffing each file versus the latest version of
each module
""" """
(options, args) = parser.parse_args(args)
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
@ -936,17 +899,9 @@ Examples:
return client.RunOnDeps('pack', args) return client.RunOnDeps('pack', args)
def CMDstatus(parser, options, args): def CMDstatus(parser, args):
"""Show the modification status of for every dependencies. """Show modification status for every dependencies."""
(options, args) = parser.parse_args(args)
Additional options and args may be passed to 'svn status'.
usage: status [options] [--] [svn diff args/options]
Valid options:
--verbose : output additional diagnostics
--nohooks : don't run the hooks after the update is complete
"""
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
@ -957,31 +912,7 @@ Valid options:
return client.RunOnDeps('status', args) return client.RunOnDeps('status', args)
def CMDsync(parser, options, args): @attr('epilog', """Examples:
"""Checkout/update the modules specified by the gclient configuration.
Unless --revision is specified, then the latest revision of the root solutions
is checked out, with dependent submodule versions updated according to DEPS
files. If --revision is specified, then the given revision is used in place
of the latest, either for a single solution or for all solutions.
Unless the --force option is provided, solutions and modules whose
local revision matches the one to update (i.e., they have not changed
in the repository) are *not* modified. Unless --nohooks is provided,
the hooks are run. See 'help config' for more information.
usage: gclient sync [options] [--] [SCM update options/args]
Valid options:
--force : force update even for unchanged modules
--nohooks : don't run the hooks after the update is complete
--revision SOLUTION@REV : update given solution to specified revision
--deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
--verbose : output additional diagnostics
--head : update to latest revision, instead of last good
revision
--reset : resets any local changes before updating (git only)
Examples:
gclient sync gclient sync
update files from SCM according to current configuration, update files from SCM according to current configuration,
*for modules which have changed since last update or sync* *for modules which have changed since last update or sync*
@ -990,7 +921,33 @@ Examples:
all modules (useful for recovering files deleted from local copy) all modules (useful for recovering files deleted from local copy)
gclient sync --revision src@31000 gclient sync --revision src@31000
update src directory to r31000 update src directory to r31000
""" """)
def CMDsync(parser, args):
"""Checkout/update all modules."""
parser.add_option("--force", action="store_true",
help="force update even for unchanged modules")
parser.add_option("--nohooks", action="store_true",
help="don't run hooks after the update is complete")
parser.add_option("-r", "--revision", action="append",
dest="revisions", metavar="REV", default=[],
help="update given solution to specified revision, "
"can be used multiple times for each solution, "
"e.g. -r src@123, -r internal@32")
parser.add_option("--head", action="store_true",
help="skips any safesync_urls specified in "
"configured solutions and sync to head instead")
parser.add_option("--delete_unversioned_trees", action="store_true",
help="delete any unexpected unversioned trees "
"that are in the checkout")
parser.add_option("--reset", action="store_true",
help="resets any local changes before updating (git only)")
parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
help="sync deps for the specified (comma-separated) "
"platform(s); 'all' will sync all platforms")
parser.add_option("--manually_grab_svn_rev", action="store_true",
help="Skip svn up whenever possible by requesting "
"actual HEAD revision from the repository")
(options, args) = parser.parse_args(args)
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
@ -1023,31 +980,13 @@ Examples:
return client.RunOnDeps('update', args) return client.RunOnDeps('update', args)
def CMDupdate(parser, options, args): def CMDupdate(parser, args):
"""Alias for the sync command. Deprecated.""" """Alias for the sync command. Deprecated."""
return CMDsync(parser, options, args) return CMDsync(parser, args)
def CMDdiff(parser, options, args):
"""Display the differences between two revisions of modules.
(Does 'svn diff' for each checked out module and dependences.) def CMDdiff(parser, args):
Additional args and options to 'svn diff' can be passed after """Displays local diff for every dependencies."""
gclient options. (options, args) = parser.parse_args(args)
usage: diff [options] [--] [svn args/options]
Valid options:
--verbose : output additional diagnostics
Examples:
gclient diff
simple 'svn diff' for configured client and dependences
gclient diff -- -x -b
use 'svn diff -x -b' to suppress whitespace-only differences
gclient diff -- -r HEAD -x -b
diff versus the latest version of each module
"""
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
@ -1058,24 +997,24 @@ Examples:
return client.RunOnDeps('diff', args) return client.RunOnDeps('diff', args)
def CMDrevert(parser, options, args): def CMDrevert(parser, args):
"""Revert every file in every managed directory in the client view.""" """Revert all modifications in every dependencies."""
parser.add_option("--nohooks", action="store_true",
help="don't run hooks after the revert is complete")
(options, args) = parser.parse_args(args)
# --force is implied.
options.force = True
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
return client.RunOnDeps('revert', args) return client.RunOnDeps('revert', args)
def CMDrunhooks(parser, options, args): def CMDrunhooks(parser, args):
"""Runs hooks for files that have been modified in the local working copy. """Runs hooks for files that have been modified in the local working copy."""
parser.add_option("--force", action="store_true", default=True,
Implies --force. help="Deprecated. No effect.")
(options, args) = parser.parse_args(args)
usage: runhooks [options]
Valid options:
--verbose : output additional diagnostics
"""
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
@ -1084,18 +1023,16 @@ Valid options:
# client dict, but more legible, and it might contain helpful comments. # client dict, but more legible, and it might contain helpful comments.
print(client.ConfigContent()) print(client.ConfigContent())
options.force = True options.force = True
options.nohooks = False
return client.RunOnDeps('runhooks', args) return client.RunOnDeps('runhooks', args)
def CMDrevinfo(parser, options, args): def CMDrevinfo(parser, args):
"""Outputs defails for every dependencies. """Outputs details for every dependencies."""
parser.add_option("--snapshot", action="store_true",
This includes source path, server URL and revision information for every help="create a snapshot file of the current "
dependency in all solutions. "version of all repositories")
(options, args) = parser.parse_args(args)
usage: revinfo [options]
"""
__pychecker__ = 'unusednames=args'
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
if not client: if not client:
raise gclient_utils.Error("client not configured; see 'gclient config'") raise gclient_utils.Error("client not configured; see 'gclient config'")
@ -1103,70 +1040,45 @@ usage: revinfo [options]
return 0 return 0
def CMDhelp(parser, options, args): def Command(name):
"""Prints general help or command-specific documentation.""" return getattr(sys.modules[__name__], 'CMD' + name, None)
def CMDhelp(parser, args):
"""Prints list of commands or help for a specific command."""
(options, args) = parser.parse_args(args)
if len(args) == 1: if len(args) == 1:
command = Command(args[0]) return Main(args + ['--help'])
if command:
print getattr(sys.modules[__name__], 'CMD' + args[0]).__doc__
return 0
parser.usage = (DEFAULT_USAGE_TEXT + '\nCommands are:\n' + '\n'.join([
' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
parser.print_help() parser.print_help()
return 0 return 0
def Command(command): def GenUsage(parser, command):
return getattr(sys.modules[__name__], 'CMD' + command, CMDhelp) """Modify an OptParse object with the function's documentation."""
obj = Command(command)
if command == 'help':
command = '<command>'
# OptParser.description prefer nicely non-formatted strings.
parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
usage = getattr(obj, 'usage', '')
parser.set_usage('%%prog %s [options] %s' % (command, usage))
parser.epilog = getattr(obj, 'epilog', None)
def Main(argv): def Main(argv):
parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT, """Doesn't parse the arguments here, just find the right subcommand to
version='%prog ' + __version__) execute."""
# Do it late so all commands are listed.
CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
parser = optparse.OptionParser(version='%prog ' + __version__)
parser.add_option("-v", "--verbose", action="count", default=0, parser.add_option("-v", "--verbose", action="count", default=0,
help="Produces additional output for diagnostics. Can be " help="Produces additional output for diagnostics. Can be "
"used up to three times for more logging info.") "used up to three times for more logging info.")
parser.add_option("--gclientfile", metavar="FILENAME", dest="config_filename", parser.add_option("--gclientfile", metavar="FILENAME", dest="config_filename",
default=os.environ.get("GCLIENT_FILE", ".gclient"), default=os.environ.get("GCLIENT_FILE", ".gclient"),
help="Specify an alternate .gclient file") help="Specify an alternate .gclient file")
# The other options will be moved eventually.
parser.add_option("--force", action="store_true",
help="(update/sync only) force update even "
"for modules which haven't changed")
parser.add_option("--nohooks", action="store_true",
help="(update/sync/revert only) prevent the hooks "
"from running")
parser.add_option("--revision", action="append", dest="revisions",
metavar="REV", default=[],
help="(update/sync only) sync to a specific "
"revision, can be used multiple times for "
"each solution, e.g. --revision=src@123, "
"--revision=internal@32")
parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
help="(update/sync only) sync deps for the "
"specified (comma-separated) platform(s); "
"'all' will sync all platforms")
parser.add_option("--reset", action="store_true",
help="(update/sync only) resets any local changes "
"before updating (git only)")
parser.add_option("--spec",
help="(config only) create a gclient file "
"containing the provided string")
parser.add_option("--manually_grab_svn_rev", action="store_true",
help="Skip svn up whenever possible by requesting "
"actual HEAD revision from the repository")
parser.add_option("--head", action="store_true",
help="skips any safesync_urls specified in "
"configured solutions")
parser.add_option("--delete_unversioned_trees", action="store_true",
help="on update, delete any unexpected "
"unversioned trees that are in the checkout")
parser.add_option("--snapshot", action="store_true",
help="(revinfo only), create a snapshot file "
"of the current version of all repositories")
parser.add_option("--name",
help="specify alternate relative solution path")
# Integrate standard options processing. # Integrate standard options processing.
old_parser = parser.parse_args old_parser = parser.parse_args
def Parse(args): def Parse(args):
@ -1176,22 +1088,22 @@ def Main(argv):
elif options.verbose > 2: elif options.verbose > 2:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
options.entries_filename = options.config_filename + "_entries" options.entries_filename = options.config_filename + "_entries"
if not hasattr(options, 'revisions'):
# GClient.RunOnDeps expects it even if not applicable.
options.revisions = []
return (options, args) return (options, args)
parser.parse_args = Parse parser.parse_args = Parse
# We don't want wordwrapping in epilog (usually examples) # We don't want wordwrapping in epilog (usually examples)
parser.format_epilog = lambda _: parser.epilog or '' parser.format_epilog = lambda _: parser.epilog or ''
if argv:
if not len(argv): command = Command(argv[0])
argv = ['help'] if command:
# Add manual support for --version as first argument. # "fix" the usage and the description now that we know the subcommand.
if argv[0] == '--version': GenUsage(parser, argv[0])
parser.print_version() return command(parser, argv[1:])
return 0 # Not a known command. Default to help.
# Add manual support for --help as first argument. GenUsage(parser, 'help')
if argv[0] == '--help': return CMDhelp(parser, argv)
argv[0] = 'help'
options, args = parser.parse_args(argv[1:])
return Command(argv[0])(parser, options, args)
if "__main__" == __name__: if "__main__" == __name__:

@ -205,14 +205,19 @@ class FakeRepos(object):
# - versioned and unversioned reference # - versioned and unversioned reference
# - relative and full reference # - relative and full reference
# - deps_os # - deps_os
# TODO(maruel):
# - var # - var
# - hooks # - hooks
# TODO(maruel):
# - File # - File
# - $matching_files
# - use_relative_paths
self._commit_svn(file_system(1, """ self._commit_svn(file_system(1, """
vars = {
'DummyVariable': 'third_party',
}
deps = { deps = {
'src/other': 'svn://%(host)s/svn/trunk/other', 'src/other': 'svn://%(host)s/svn/trunk/other',
'src/third_party/fpp': '/trunk/third_party/foo', 'src/third_party/fpp': '/trunk/' + Var('DummyVariable') + '/foo',
} }
deps_os = { deps_os = {
'mac': { 'mac': {
@ -225,6 +230,21 @@ deps = {
'src/other': 'svn://%(host)s/svn/trunk/other', 'src/other': 'svn://%(host)s/svn/trunk/other',
'src/third_party/foo': '/trunk/third_party/foo@1', 'src/third_party/foo': '/trunk/third_party/foo@1',
} }
# I think this is wrong to have the hooks run from the base of the gclient
# checkout. It's maybe a bit too late to change that behavior.
hooks = [
{
'pattern': '.',
'action': ['python', '-c',
'open(\\'src/hooked1\\', \\'w\\').write(\\'hooked1\\')'],
},
{
# Should not be run.
'pattern': 'nonexistent',
'action': ['python', '-c',
'open(\\'src/hooked2\\', \\'w\\').write(\\'hooked2\\')'],
},
]
""" % { 'host': '127.0.0.1' })) """ % { 'host': '127.0.0.1' }))
def setUpGIT(self): def setUpGIT(self):
@ -241,15 +261,20 @@ deps = {
# - versioned and unversioned reference # - versioned and unversioned reference
# - relative and full reference # - relative and full reference
# - deps_os # - deps_os
# TODO(maruel):
# - var # - var
# - hooks # - hooks
# TODO(maruel):
# - File # - File
# - $matching_files
# - use_relative_paths
self._commit_git('repo_1', { self._commit_git('repo_1', {
'DEPS': """ 'DEPS': """
vars = {
'DummyVariable': 'repo',
}
deps = { deps = {
'src/repo2': 'git://%(host)s/git/repo_2', 'src/repo2': 'git://%(host)s/git/repo_2',
'src/repo2/repo3': '/repo_3', 'src/repo2/repo3': '/' + Var('DummyVariable') + '_3',
} }
deps_os = { deps_os = {
'mac': { 'mac': {
@ -289,6 +314,21 @@ deps = {
'src/repo2': 'git://%(host)s/git/repo_2@%(hash)s', 'src/repo2': 'git://%(host)s/git/repo_2@%(hash)s',
'src/repo2/repo_renamed': '/repo_3', 'src/repo2/repo_renamed': '/repo_3',
} }
# I think this is wrong to have the hooks run from the base of the gclient
# checkout. It's maybe a bit too late to change that behavior.
hooks = [
{
'pattern': '.',
'action': ['python', '-c',
'open(\\'src/hooked1\\', \\'w\\').write(\\'hooked1\\')'],
},
{
# Should not be run.
'pattern': 'nonexistent',
'action': ['python', '-c',
'open(\\'src/hooked2\\', \\'w\\').write(\\'hooked2\\')'],
},
]
""" % { 'host': '127.0.0.1', 'hash': self.git_hashes['repo_2'][0][0] }, """ % { 'host': '127.0.0.1', 'hash': self.git_hashes['repo_2'][0][0] },
'origin': "git/repo_1@2\n" 'origin': "git/repo_1@2\n"
}) })

@ -13,6 +13,7 @@ This test assumes GClientSmokeBase.URL_BASE is valid.
import logging import logging
import os import os
import pprint import pprint
import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
@ -106,15 +107,20 @@ class GClientSmokeBase(unittest.TestCase):
(stdout, stderr) = process.communicate() (stdout, stderr) = process.communicate()
return (stdout, stderr, process.returncode) return (stdout, stderr, process.returncode)
def check(self, expected, results): def checkString(self, expected, result):
def checkString(expected, result):
if expected != result: if expected != result:
# Strip the begining
while expected and result and expected[0] == result[0]: while expected and result and expected[0] == result[0]:
expected = expected[1:] expected = expected[1:]
result = result[1:] result = result[1:]
# The exception trace makes it hard to read so dump it too.
if '\n' in result:
print result
self.assertEquals(expected, result) self.assertEquals(expected, result)
checkString(expected[0], results[0])
checkString(expected[1], results[1]) def check(self, expected, results):
self.checkString(expected[0], results[0])
self.checkString(expected[1], results[1])
self.assertEquals(expected[2], results[2]) self.assertEquals(expected[2], results[2])
def assertTree(self, tree): def assertTree(self, tree):
@ -131,7 +137,7 @@ class GClientSmoke(GClientSmokeBase):
def testCommands(self): def testCommands(self):
"""This test is to make sure no new command was added.""" """This test is to make sure no new command was added."""
result = self.gclient(['help']) result = self.gclient(['help'])
self.assertEquals(3189, len(result[0])) self.assertEquals(1197, len(result[0]))
self.assertEquals(0, len(result[1])) self.assertEquals(0, len(result[1]))
self.assertEquals(0, result[2]) self.assertEquals(0, result[2])
@ -152,12 +158,13 @@ class GClientSmoke(GClientSmokeBase):
class GClientSmokeSVN(GClientSmokeBase): class GClientSmokeSVN(GClientSmokeBase):
"""sync is the most important command. Hence test it more.""" """sync is the most important command. Hence test it more."""
def testSync(self): def testSync(self):
"""Test pure gclient svn checkout, example of Chromium checkout"""
self.gclient(['config', self.svn_base + 'trunk/src/']) self.gclient(['config', self.svn_base + 'trunk/src/'])
# Test unversioned checkout. # Test unversioned checkout.
results = self.gclient(['sync', '--deps', 'mac']) results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0]) logging.debug(results[0])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
self.assertEquals(17, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_svn_tree( tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[-1]), (join('trunk', 'src'), 'src', FAKE.svn_revs[-1]),
@ -165,13 +172,19 @@ class GClientSmokeSVN(GClientSmokeBase):
FAKE.svn_revs[1]), FAKE.svn_revs[1]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]), (join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
) )
tree[join('src', 'hooked1')] = 'hooked1'
self.assertTree(tree) self.assertTree(tree)
# Manually remove hooked1 before synching to make sure it's not recreated.
os.remove(join(self.root_dir, 'src', 'hooked1'))
# Test incremental versioned sync: sync backward. # Test incremental versioned sync: sync backward.
results = self.gclient(['sync', '--revision', 'src@1', '--deps', 'mac', results = self.gclient(['sync', '--revision', 'src@1', '--deps', 'mac',
'--delete_unversioned_trees']) '--delete_unversioned_trees'])
logging.debug(results[0]) logging.debug(results[0])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
self.assertEquals(19, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_svn_tree( tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[1]), (join('trunk', 'src'), 'src', FAKE.svn_revs[1]),
@ -186,7 +199,9 @@ class GClientSmokeSVN(GClientSmokeBase):
# Test incremental sync: delete-unversioned_trees isn't there. # Test incremental sync: delete-unversioned_trees isn't there.
results = self.gclient(['sync', '--deps', 'mac']) results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0]) logging.debug(results[0])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
self.assertEquals(21, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_svn_tree( tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[-1]), (join('trunk', 'src'), 'src', FAKE.svn_revs[-1]),
@ -199,28 +214,35 @@ class GClientSmokeSVN(GClientSmokeBase):
join('src', 'third_party', 'prout'), join('src', 'third_party', 'prout'),
FAKE.svn_revs[2]), FAKE.svn_revs[2]),
) )
tree[join('src', 'hooked1')] = 'hooked1'
self.assertTree(tree) self.assertTree(tree)
def testRevertAndStatus(self): def testRevertAndStatus(self):
self.gclient(['config', self.svn_base + 'trunk/src/']) self.gclient(['config', self.svn_base + 'trunk/src/'])
results = self.gclient(['sync', '--deps', 'mac']) # Tested in testSync.
self.gclient(['sync', '--deps', 'mac'])
write(join(self.root_dir, 'src', 'third_party', 'foo', 'hi'), 'Hey!') write(join(self.root_dir, 'src', 'third_party', 'foo', 'hi'), 'Hey!')
results = self.gclient(['status']) results = self.gclient(['status'])
out = results[0].splitlines(False) out = results[0].splitlines(False)
self.assertEquals(7, len(out))
self.assertEquals(out[0], '') self.assertEquals(out[0], '')
self.assertTrue(out[1].startswith('________ running \'svn status\' in \'')) self.assertTrue(out[1].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[2], '? other') self.assertEquals(out[2], '? other')
self.assertEquals(out[3], '? third_party/foo') self.assertEquals(out[3], '? hooked1')
self.assertEquals(out[4], '') self.assertEquals(out[4], '? third_party/foo')
self.assertTrue(out[5].startswith('________ running \'svn status\' in \'')) self.assertEquals(out[5], '')
self.assertEquals(out[6], '? hi') self.assertTrue(out[6].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[7], '? hi')
self.assertEquals(8, len(out))
self.assertEquals('', results[1]) self.assertEquals('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
# Revert implies --force implies running hooks without looking at pattern
# matching.
results = self.gclient(['revert']) results = self.gclient(['revert'])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
self.assertEquals(22, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_svn_tree( tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[-1]), (join('trunk', 'src'), 'src', FAKE.svn_revs[-1]),
@ -228,26 +250,50 @@ class GClientSmokeSVN(GClientSmokeBase):
FAKE.svn_revs[1]), FAKE.svn_revs[1]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]), (join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
) )
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree) self.assertTree(tree)
results = self.gclient(['status']) results = self.gclient(['status'])
out = results[0].splitlines(False) out = results[0].splitlines(False)
self.assertEquals(4, len(out))
self.assertEquals(out[0], '') self.assertEquals(out[0], '')
self.assertTrue(out[1].startswith('________ running \'svn status\' in \'')) self.assertTrue(out[1].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[2], '? other') self.assertEquals(out[2], '? other')
self.assertEquals(out[3], '? third_party/foo') self.assertEquals(out[3], '? hooked1')
self.assertEquals('', results[1]) self.assertEquals(out[4], '? hooked2')
self.assertEquals(out[5], '? third_party/foo')
self.assertEquals(6, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
def testRunHooks(self):
self.gclient(['config', self.svn_base + 'trunk/src/'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['runhooks'])
out = results[0].splitlines(False)
self.assertEquals(4, len(out))
self.assertEquals(out[0], '')
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked1\', \'w\'\)\.write\(\'hooked1\'\)\' in \'.*',
out[1]))
self.assertEquals(out[2], '')
# runhooks runs all hooks even if not matching by design.
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked2\', \'w\'\)\.write\(\'hooked2\'\)\' in \'.*',
out[3]))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
class GClientSmokeGIT(GClientSmokeBase): class GClientSmokeGIT(GClientSmokeBase):
def testSyncGit(self): def testSync(self):
"""Test pure gclient git checkout, example of Chromium OS checkout"""
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src']) self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
# Test unversioned checkout. # Test unversioned checkout.
results = self.gclient(['sync', '--deps', 'mac']) results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0]) out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3582 hooks run even if not matching, must
# add sync parsing to get the list of updated files.
self.assertEquals(13, len(out))
self.assertTrue(results[1].startswith('Switched to a new branch \'')) self.assertTrue(results[1].startswith('Switched to a new branch \''))
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_git_tree( tree = mangle_git_tree(
@ -255,14 +301,21 @@ class GClientSmokeGIT(GClientSmokeBase):
(join('src', 'repo2'), FAKE.git_hashes['repo_2'][0][1]), (join('src', 'repo2'), FAKE.git_hashes['repo_2'][0][1]),
(join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]), (join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]),
) )
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree) self.assertTree(tree)
# Manually remove hooked1 before synching to make sure it's not recreated.
os.remove(join(self.root_dir, 'src', 'hooked1'))
# Test incremental versioned sync: sync backward. # Test incremental versioned sync: sync backward.
results = self.gclient(['sync', '--revision', results = self.gclient(['sync', '--revision',
'src@' + FAKE.git_hashes['repo_1'][0][0], 'src@' + FAKE.git_hashes['repo_1'][0][0],
'--deps', 'mac', '--delete_unversioned_trees']) '--deps', 'mac', '--delete_unversioned_trees'])
logging.debug(results[0]) logging.debug(results[0])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
self.assertEquals(20, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_git_tree( tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][0][1]), ('src', FAKE.git_hashes['repo_1'][0][1]),
@ -270,11 +323,14 @@ class GClientSmokeGIT(GClientSmokeBase):
(join('src', 'repo2', 'repo3'), FAKE.git_hashes['repo_3'][1][1]), (join('src', 'repo2', 'repo3'), FAKE.git_hashes['repo_3'][1][1]),
(join('src', 'repo4'), FAKE.git_hashes['repo_4'][1][1]), (join('src', 'repo4'), FAKE.git_hashes['repo_4'][1][1]),
) )
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree) self.assertTree(tree)
# Test incremental sync: delete-unversioned_trees isn't there. # Test incremental sync: delete-unversioned_trees isn't there.
results = self.gclient(['sync', '--deps', 'mac']) results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0]) logging.debug(results[0])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
self.assertEquals(25, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_git_tree( tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][1][1]), ('src', FAKE.git_hashes['repo_1'][1][1]),
@ -283,42 +339,66 @@ class GClientSmokeGIT(GClientSmokeBase):
(join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]), (join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]),
(join('src', 'repo4'), FAKE.git_hashes['repo_4'][1][1]), (join('src', 'repo4'), FAKE.git_hashes['repo_4'][1][1]),
) )
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree) self.assertTree(tree)
def testRevertAndStatus(self): def testRevertAndStatus(self):
"""TODO(maruel): Remove this line once this test is fixed.""" """TODO(maruel): Remove this line once this test is fixed."""
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src']) self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
results = self.gclient(['sync', '--deps', 'mac']) # Tested in testSync.
self.gclient(['sync', '--deps', 'mac'])
write(join(self.root_dir, 'src', 'repo2', 'hi'), 'Hey!') write(join(self.root_dir, 'src', 'repo2', 'hi'), 'Hey!')
results = self.gclient(['status']) results = self.gclient(['status'])
out = results[0].splitlines(False) out = results[0].splitlines(False)
# TODO(maruel): THIS IS WRONG. # TODO(maruel): http://crosbug.com/3584 It should output the unversioned
# files.
self.assertEquals(0, len(out)) self.assertEquals(0, len(out))
# Revert implies --force implies running hooks without looking at pattern
# matching.
results = self.gclient(['revert']) results = self.gclient(['revert'])
self.assertEquals('', results[1]) out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3583 It just runs the hooks right now.
self.assertEquals(7, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2]) self.assertEquals(0, results[2])
tree = mangle_git_tree( tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][1][1]), ('src', FAKE.git_hashes['repo_1'][1][1]),
(join('src', 'repo2'), FAKE.git_hashes['repo_2'][0][1]), (join('src', 'repo2'), FAKE.git_hashes['repo_2'][0][1]),
(join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]), (join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]),
) )
# TODO(maruel): THIS IS WRONG. # TODO(maruel): http://crosbug.com/3583 This file should have been removed.
tree[join('src', 'repo2', 'hi')] = 'Hey!' tree[join('src', 'repo2', 'hi')] = 'Hey!'
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree) self.assertTree(tree)
results = self.gclient(['status']) results = self.gclient(['status'])
out = results[0].splitlines(False) out = results[0].splitlines(False)
# TODO(maruel): THIS IS WRONG. # TODO(maruel): http://crosbug.com/3584 It should output the unversioned
# files.
self.assertEquals(0, len(out)) self.assertEquals(0, len(out))
def testRunHooks(self):
class GClientSmokeRevInfo(GClientSmokeBase): self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
"""revert is the second most important command. Hence test it more.""" self.gclient(['sync', '--deps', 'mac'])
def setUp(self): results = self.gclient(['runhooks'])
GClientSmokeBase.setUp(self) logging.debug(results[0])
self.gclient(['config', self.URL_BASE]) out = results[0].splitlines(False)
self.assertEquals(4, len(out))
self.assertEquals(out[0], '')
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked1\', \'w\'\)\.write\(\'hooked1\'\)\' in \'.*',
out[1]))
self.assertEquals(out[2], '')
# runhooks runs all hooks even if not matching by design.
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked2\', \'w\'\)\.write\(\'hooked2\'\)\' in \'.*',
out[3]))
self.checkString('', results[1])
self.assertEquals(0, results[2])
if __name__ == '__main__': if __name__ == '__main__':

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save