From: Brian Warner Date: Mon, 18 Jun 2012 17:43:49 +0000 (-0700) Subject: bin/tahoe: clean up global-vs-subcommand arguments like --node-directory X-Git-Tag: allmydata-tahoe-1.10b1~16 X-Git-Url: https://git.rkrishnan.org/specifications/%5B/%5D%20/flags/rgr-080307.php?a=commitdiff_plain;h=5874a7d3a752a451b3cce73e8457bde294ff529f;p=tahoe-lafs%2Ftahoe-lafs.git bin/tahoe: clean up global-vs-subcommand arguments like --node-directory The new rules for "bin/tahoe ARG1.. SUBCOMMAND ARG2.." arg: * --node-directory is only accepted in ARG1, not ARG2 * create-*/start/stop/restart accept --basedir in ARG2, or an explicit basedir argument * only one of --node-directory/--basedir/explicit-basedir is accepted * --quiet/--version is only accepted in ARG1, not ARG2 Closes #166 --- diff --git a/src/allmydata/scripts/admin.py b/src/allmydata/scripts/admin.py index 581224d6..1a6c9854 100644 --- a/src/allmydata/scripts/admin.py +++ b/src/allmydata/scripts/admin.py @@ -1,12 +1,13 @@ from twisted.python import usage +from allmydata.scripts.common import BaseOptions -class GenerateKeypairOptions(usage.Options): +class GenerateKeypairOptions(BaseOptions): def getSynopsis(self): return "Usage: tahoe admin generate-keypair" def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Generate a public/private keypair, dumped to stdout as two lines of ASCII.. @@ -20,7 +21,7 @@ def print_keypair(options): print >>out, "private:", privkey_vs print >>out, "public:", pubkey_vs -class DerivePubkeyOptions(usage.Options): +class DerivePubkeyOptions(BaseOptions): def parseArgs(self, privkey): self.privkey = privkey @@ -28,7 +29,7 @@ class DerivePubkeyOptions(usage.Options): return "Usage: tahoe admin derive-pubkey PRIVKEY" def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Given a private (signing) key that was previously generated with generate-keypair, derive the public key and print it to stdout. @@ -45,7 +46,7 @@ def derive_pubkey(options): print >>out, "public:", pubkey_vs return 0 -class AdminCommand(usage.Options): +class AdminCommand(BaseOptions): subCommands = [ ("generate-keypair", None, GenerateKeypairOptions, "Generate a public/private keypair, write to stdout."), @@ -58,7 +59,7 @@ class AdminCommand(usage.Options): def getSynopsis(self): return "Usage: tahoe admin SUBCOMMAND" def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Please run e.g. 'tahoe admin generate-keypair --help' for more details on each subcommand. diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py index 343a5728..5082685c 100644 --- a/src/allmydata/scripts/cli.py +++ b/src/allmydata/scripts/cli.py @@ -1,6 +1,7 @@ import os.path, re, fnmatch from twisted.python import usage -from allmydata.scripts.common import BaseOptions, get_aliases, get_default_nodedir, DEFAULT_ALIAS +from allmydata.scripts.common import get_aliases, get_default_nodedir, \ + DEFAULT_ALIAS, BaseOptions from allmydata.util.encodingutil import argv_to_unicode, argv_to_abspath, quote_output NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?") @@ -9,23 +10,18 @@ _default_nodedir = get_default_nodedir() class VDriveOptions(BaseOptions): optParameters = [ - ["node-directory", "d", None, - "Specify which Tahoe node directory should be used. The directory " - "should either contain a full Tahoe node, or a file named node.url " - "that points to some other Tahoe node. It should also contain a file " - "named '" + os.path.join('private', 'aliases') + "' which contains the " - "mapping from alias name to root dirnode URI." + ( - _default_nodedir and (" [default: " + quote_output(_default_nodedir) + "]") or "")], ["node-url", "u", None, - "Specify the URL of the Tahoe gateway node, such as 'http://127.0.0.1:3456'. " + "Specify the URL of the Tahoe gateway node, such as " + "'http://127.0.0.1:3456'. " "This overrides the URL found in the --node-directory ."], ["dir-cap", None, None, "Specify which dirnode URI should be used as the 'tahoe' alias."] ] def postOptions(self): - if self['node-directory']: - self['node-directory'] = argv_to_abspath(self['node-directory']) + self["quiet"] = self.parent["quiet"] + if self.parent['node-directory']: + self['node-directory'] = argv_to_abspath(self.parent['node-directory']) else: self['node-directory'] = _default_nodedir diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index cd545c3c..27ea9af1 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -25,58 +25,42 @@ def get_default_nodedir(): class BaseOptions(usage.Options): - # unit tests can override these to point at StringIO instances - stdin = sys.stdin - stdout = sys.stdout - stderr = sys.stderr - - optFlags = [ - ["quiet", "q", "Operate silently."], - ["version", "V", "Display version numbers."], - ["version-and-path", None, "Display version numbers and paths to their locations."], - ] - optParameters = [ - ["node-directory", "d", None, "Specify which Tahoe node directory should be used." + ( - _default_nodedir and (" [default for most commands: " + quote_output(_default_nodedir) + "]") or "")], - ] - def __init__(self): super(BaseOptions, self).__init__() self.command_name = os.path.basename(sys.argv[0]) if self.command_name == 'trial': self.command_name = 'tahoe' + # Only allow "tahoe --version", not e.g. "tahoe start --version" def opt_version(self): - import allmydata - print >>self.stdout, allmydata.get_package_versions_string(debug=True) - self.no_command_needed = True + raise usage.UsageError("--version not allowed on subcommands") - def opt_version_and_path(self): - import allmydata - print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True) - self.no_command_needed = True - - -class BasedirMixin: +class BasedirOptions(BaseOptions): default_nodedir = _default_nodedir optParameters = [ - ["basedir", "C", None, "Same as --node-directory."], + ["basedir", "C", None, "Same as --node-directory (default %s)." + % get_default_nodedir()], ] def parseArgs(self, basedir=None): - if self['node-directory'] and self['basedir']: - raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) " - "options cannot both be used.") + if self.parent['node-directory'] and self['basedir']: + raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) options cannot both be used.") + if self.parent['node-directory'] and basedir: + raise usage.UsageError("The --node-directory (or -d) option and a basedir argument cannot both be used.") + if self['basedir'] and basedir: + raise usage.UsageError("The --basedir (or -C) option and a basedir argument cannot both be used.") if basedir: b = argv_to_abspath(basedir) elif self['basedir']: b = argv_to_abspath(self['basedir']) - elif self['node-directory']: - b = argv_to_abspath(self['node-directory']) - else: + elif self.parent['node-directory']: + b = argv_to_abspath(self.parent['node-directory']) + elif self.default_nodedir: b = self.default_nodedir + else: + raise usage.UsageError("No default basedir available, you must provide one with --node-directory, --basedir, or a basedir argument") self['basedir'] = b def postOptions(self): diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py index 7e9dcf79..893bf08c 100644 --- a/src/allmydata/scripts/create_node.py +++ b/src/allmydata/scripts/create_node.py @@ -1,11 +1,11 @@ import os, sys -from allmydata.scripts.common import BasedirMixin, BaseOptions +from allmydata.scripts.common import BasedirOptions from allmydata.util.assertutil import precondition from allmydata.util.encodingutil import listdir_unicode, argv_to_unicode, quote_output import allmydata -class CreateClientOptions(BasedirMixin, BaseOptions): +class CreateClientOptions(BasedirOptions): optParameters = [ # we provide 'create-node'-time options for the most common # configuration knobs. The rest can be controlled by editing @@ -29,13 +29,9 @@ class CreateNodeOptions(CreateClientOptions): return "Usage: %s create-node [options] [NODEDIR]" % (self.command_name,) -class CreateIntroducerOptions(BasedirMixin, BaseOptions): +class CreateIntroducerOptions(BasedirOptions): default_nodedir = None - optParameters = [ - ["node-directory", "d", None, "Specify which directory the introducer should be created in. [no default]"], - ] - def getSynopsis(self): return "Usage: %s create-introducer [options] NODEDIR" % (self.command_name,) diff --git a/src/allmydata/scripts/debug.py b/src/allmydata/scripts/debug.py index f7749cd4..cb8e986a 100644 --- a/src/allmydata/scripts/debug.py +++ b/src/allmydata/scripts/debug.py @@ -6,9 +6,10 @@ from twisted.python import usage, failure from twisted.internet import defer from twisted.scripts import trial as twisted_trial from foolscap.logging import cli as foolscap_cli +from allmydata.scripts.common import BaseOptions -class DumpOptions(usage.Options): +class DumpOptions(BaseOptions): def getSynopsis(self): return "Usage: tahoe debug dump-share SHARE_FILENAME" @@ -18,7 +19,7 @@ class DumpOptions(usage.Options): ] def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Print lots of information about the given share, by parsing the share's contents. This includes share type, lease information, encoding parameters, @@ -405,7 +406,7 @@ def dump_MDMF_share(m, length, options): -class DumpCapOptions(usage.Options): +class DumpCapOptions(BaseOptions): def getSynopsis(self): return "Usage: tahoe debug dump-cap [options] FILECAP" optParameters = [ @@ -420,7 +421,7 @@ class DumpCapOptions(usage.Options): self.cap = cap def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Print information about the given cap-string (aka: URI, file-cap, dir-cap, read-cap, write-cap). The URI string is parsed and unpacked. This prints the @@ -607,7 +608,7 @@ def dump_uri_instance(u, nodeid, secret, out, show_header=True): else: print >>out, "unknown cap type" -class FindSharesOptions(usage.Options): +class FindSharesOptions(BaseOptions): def getSynopsis(self): return "Usage: tahoe debug find-shares STORAGE_INDEX NODEDIRS.." @@ -617,7 +618,7 @@ class FindSharesOptions(usage.Options): self.nodedirs = map(argv_to_abspath, nodedirs) def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Locate all shares for the given storage index. This command looks through one or more node directories to find the shares. It returns a list of filenames, @@ -657,7 +658,7 @@ def find_shares(options): return 0 -class CatalogSharesOptions(usage.Options): +class CatalogSharesOptions(BaseOptions): """ """ @@ -671,7 +672,7 @@ class CatalogSharesOptions(usage.Options): return "Usage: tahoe debug catalog-shares NODEDIRS.." def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Locate all shares in the given node directories, and emit a one-line summary of each share. Run it like this: @@ -879,7 +880,7 @@ def catalog_shares_one_abbrevdir(si_s, si_dir, now, out, err): print >>err, "Error processing %s" % quote_output(si_dir) failure.Failure().printTraceback(err) -class CorruptShareOptions(usage.Options): +class CorruptShareOptions(BaseOptions): def getSynopsis(self): return "Usage: tahoe debug corrupt-share SHARE_FILENAME" @@ -888,7 +889,7 @@ class CorruptShareOptions(usage.Options): ] def getUsage(self, width=None): - t = usage.Options.getUsage(self, width) + t = BaseOptions.getUsage(self, width) t += """ Corrupt the given share by flipping a bit. This will cause a verifying/downloading client to log an integrity-check failure incident, and @@ -959,7 +960,7 @@ def corrupt_share(options): -class ReplOptions(usage.Options): +class ReplOptions(BaseOptions): def getSynopsis(self): return "Usage: tahoe debug repl" @@ -1042,7 +1043,7 @@ def flogtool(config): return foolscap_cli.run_flogtool() -class DebugCommand(usage.Options): +class DebugCommand(BaseOptions): subCommands = [ ["dump-share", None, DumpOptions, "Unpack and display the contents of a share (uri_extension and leases)."], @@ -1060,7 +1061,7 @@ class DebugCommand(usage.Options): def getSynopsis(self): return "" def getUsage(self, width=None): - #t = usage.Options.getUsage(self, width) + #t = BaseOptions.getUsage(self, width) t = """Usage: tahoe debug SUBCOMMAND Subcommands: tahoe debug dump-share Unpack and display the contents of a share. diff --git a/src/allmydata/scripts/keygen.py b/src/allmydata/scripts/keygen.py index 1f5c30f5..5b1c9ab5 100644 --- a/src/allmydata/scripts/keygen.py +++ b/src/allmydata/scripts/keygen.py @@ -1,16 +1,12 @@ import os, sys -from allmydata.scripts.common import BasedirMixin, BaseOptions +from allmydata.scripts.common import BasedirOptions from allmydata.util.assertutil import precondition from allmydata.util.encodingutil import listdir_unicode, quote_output -class CreateKeyGeneratorOptions(BasedirMixin, BaseOptions): +class CreateKeyGeneratorOptions(BasedirOptions): default_nodedir = None - optParameters = [ - ["node-directory", "d", None, "Specify which directory the key-generator should be created in. [no default]"], - ] - def getSynopsis(self): return "Usage: %s create-key-generator [options] NODEDIR" % (self.command_name,) diff --git a/src/allmydata/scripts/runner.py b/src/allmydata/scripts/runner.py index 210efb65..c89a2f45 100644 --- a/src/allmydata/scripts/runner.py +++ b/src/allmydata/scripts/runner.py @@ -1,10 +1,10 @@ -import sys +import os, sys from cStringIO import StringIO from twisted.python import usage -from allmydata.scripts.common import BaseOptions +from allmydata.scripts.common import get_default_nodedir from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin from allmydata.util.encodingutil import quote_output, get_io_encoding @@ -15,7 +15,24 @@ def GROUP(s): return [("\n" + s, None, None, None)] -class Options(BaseOptions, usage.Options): +_default_nodedir = get_default_nodedir() + +NODEDIR_HELP = ("Specify which Tahoe node directory should be used. The " + "directory should either contain a full Tahoe node, or a " + "file named node.url that points to some other Tahoe node. " + "It should also contain a file named '" + + os.path.join('private', 'aliases') + + "' which contains the mapping from alias name to root " + "dirnode URI.") +if _default_nodedir: + NODEDIR_HELP += " [default for most commands: " + quote_output(_default_nodedir) + "]" + +class Options(usage.Options): + # unit tests can override these to point at StringIO instances + stdin = sys.stdin + stdout = sys.stdout + stderr = sys.stderr + synopsis = "\nUsage: tahoe [command options]" subCommands = ( GROUP("Administration") + create_node.subCommands @@ -30,6 +47,28 @@ class Options(BaseOptions, usage.Options): + cli.subCommands ) + optFlags = [ + ["quiet", "q", "Operate silently."], + ["version", "V", "Display version numbers."], + ["version-and-path", None, "Display version numbers and paths to their locations."], + ] + optParameters = [ + ["node-directory", "d", None, NODEDIR_HELP], + ] + + def opt_version(self): + import allmydata + print >>self.stdout, allmydata.get_package_versions_string(debug=True) + self.no_command_needed = True + + def opt_version_and_path(self): + import allmydata + print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True) + self.no_command_needed = True + + def getSynopsis(self): + return "\nUsage: tahoe [global-options] [command-options]" + def getUsage(self, **kwargs): t = usage.Options.getUsage(self, **kwargs) return t + "\nPlease run 'tahoe --help' for more details on each command.\n" diff --git a/src/allmydata/scripts/startstop_node.py b/src/allmydata/scripts/startstop_node.py index 5045bd61..36138e00 100644 --- a/src/allmydata/scripts/startstop_node.py +++ b/src/allmydata/scripts/startstop_node.py @@ -1,12 +1,12 @@ import os, sys, signal, time -from allmydata.scripts.common import BasedirMixin, BaseOptions +from allmydata.scripts.common import BasedirOptions from allmydata.util import fileutil from allmydata.util.assertutil import precondition from allmydata.util.encodingutil import listdir_unicode, quote_output -class StartOptions(BasedirMixin, BaseOptions): +class StartOptions(BasedirOptions): optFlags = [ ["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."], ["syslog", None, "Tell the node to log to syslog, not a file."], @@ -16,12 +16,12 @@ class StartOptions(BasedirMixin, BaseOptions): return "Usage: %s start [options] [NODEDIR]" % (self.command_name,) -class StopOptions(BasedirMixin, BaseOptions): +class StopOptions(BasedirOptions): def getSynopsis(self): return "Usage: %s stop [options] [NODEDIR]" % (self.command_name,) -class RestartOptions(BasedirMixin, BaseOptions): +class RestartOptions(BasedirOptions): optFlags = [ ["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."], ["syslog", None, "Tell the node to log to syslog, not a file."], @@ -31,13 +31,9 @@ class RestartOptions(BasedirMixin, BaseOptions): return "Usage: %s restart [options] [NODEDIR]" % (self.command_name,) -class RunOptions(BasedirMixin, BaseOptions): +class RunOptions(BasedirOptions): default_nodedir = u"." - optParameters = [ - ["node-directory", "d", None, "Specify the directory of the node to be run. [default, for 'tahoe run' only: current directory]"], - ] - def getSynopsis(self): return "Usage: %s run [options] [NODEDIR]" % (self.command_name,) diff --git a/src/allmydata/scripts/stats_gatherer.py b/src/allmydata/scripts/stats_gatherer.py index 230d4a90..bdb35766 100644 --- a/src/allmydata/scripts/stats_gatherer.py +++ b/src/allmydata/scripts/stats_gatherer.py @@ -1,16 +1,12 @@ import os, sys -from allmydata.scripts.common import BasedirMixin, BaseOptions +from allmydata.scripts.common import BasedirOptions from allmydata.util.assertutil import precondition from allmydata.util.encodingutil import listdir_unicode, quote_output -class CreateStatsGathererOptions(BasedirMixin, BaseOptions): +class CreateStatsGathererOptions(BasedirOptions): default_nodedir = None - optParameters = [ - ["node-directory", "d", None, "Specify which directory the stats-gatherer should be created in. [no default]"], - ] - def getSynopsis(self): return "Usage: %s create-stats-gatherer [options] NODEDIR" % (self.command_name,) diff --git a/src/allmydata/test/test_cli.py b/src/allmydata/test/test_cli.py index 222bbba5..22886d0a 100644 --- a/src/allmydata/test/test_cli.py +++ b/src/allmydata/test/test_cli.py @@ -44,13 +44,19 @@ from allmydata.util.fileutil import abspath_expanduser_unicode timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s +def parse_options(basedir, command, args): + o = runner.Options() + o.parseOptions(["--node-directory", basedir, command] + args) + while hasattr(o, "subOptions"): + o = o.subOptions + return o class CLITestMixin(ReallyEqualMixin): def do_cli(self, verb, *args, **kwargs): nodeargs = [ "--node-directory", self.get_clientdir(), ] - argv = [verb] + nodeargs + list(args) + argv = nodeargs + [verb] + list(args) stdin = kwargs.get("stdin", "") stdout, stderr = StringIO(), StringIO() d = threads.deferToThread(runner.runner, argv, run_by_human=False, @@ -73,69 +79,6 @@ class CLITestMixin(ReallyEqualMixin): class CLI(CLITestMixin, unittest.TestCase): - # this test case only looks at argument-processing and simple stuff. - def test_options(self): - fileutil.rm_dir("cli/test_options") - fileutil.make_dirs("cli/test_options") - fileutil.make_dirs("cli/test_options/private") - fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n") - filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16, - fingerprint="\x00"*32) - private_uri = uri.DirectoryURI(filenode_uri).to_string() - fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n") - o = cli.ListOptions() - o.parseOptions(["--node-directory", "cli/test_options"]) - self.failUnlessReallyEqual(o['node-url'], "http://localhost:8080/") - self.failUnlessReallyEqual(o.aliases[DEFAULT_ALIAS], private_uri) - self.failUnlessReallyEqual(o.where, u"") - - o = cli.ListOptions() - o.parseOptions(["--node-directory", "cli/test_options", - "--node-url", "http://example.org:8111/"]) - self.failUnlessReallyEqual(o['node-url'], "http://example.org:8111/") - self.failUnlessReallyEqual(o.aliases[DEFAULT_ALIAS], private_uri) - self.failUnlessReallyEqual(o.where, u"") - - o = cli.ListOptions() - o.parseOptions(["--node-directory", "cli/test_options", - "--dir-cap", "root"]) - self.failUnlessReallyEqual(o['node-url'], "http://localhost:8080/") - self.failUnlessReallyEqual(o.aliases[DEFAULT_ALIAS], "root") - self.failUnlessReallyEqual(o.where, u"") - - o = cli.ListOptions() - other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16, - fingerprint="\x11"*32) - other_uri = uri.DirectoryURI(other_filenode_uri).to_string() - o.parseOptions(["--node-directory", "cli/test_options", - "--dir-cap", other_uri]) - self.failUnlessReallyEqual(o['node-url'], "http://localhost:8080/") - self.failUnlessReallyEqual(o.aliases[DEFAULT_ALIAS], other_uri) - self.failUnlessReallyEqual(o.where, u"") - - o = cli.ListOptions() - o.parseOptions(["--node-directory", "cli/test_options", - "--dir-cap", other_uri, "subdir"]) - self.failUnlessReallyEqual(o['node-url'], "http://localhost:8080/") - self.failUnlessReallyEqual(o.aliases[DEFAULT_ALIAS], other_uri) - self.failUnlessReallyEqual(o.where, u"subdir") - - o = cli.ListOptions() - self.failUnlessRaises(usage.UsageError, - o.parseOptions, - ["--node-directory", "cli/test_options", - "--node-url", "NOT-A-URL"]) - - o = cli.ListOptions() - o.parseOptions(["--node-directory", "cli/test_options", - "--node-url", "http://localhost:8080"]) - self.failUnlessReallyEqual(o["node-url"], "http://localhost:8080/") - - o = cli.ListOptions() - o.parseOptions(["--node-directory", "cli/test_options", - "--node-url", "https://localhost/"]) - self.failUnlessReallyEqual(o["node-url"], "https://localhost/") - def _dump_cap(self, *args): config = debug.DumpCapOptions() config.stdout,config.stderr = StringIO(), StringIO() @@ -730,11 +673,11 @@ class Help(unittest.TestCase): class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase): def _test_webopen(self, args, expected_url): - woo = cli.WebopenOptions() - all_args = ["--node-directory", self.get_clientdir()] + list(args) - woo.parseOptions(all_args) + o = runner.Options() + o.parseOptions(["--node-directory", self.get_clientdir(), "webopen"] + + list(args)) urls = [] - rc = cli.webopen(woo, urls.append) + rc = cli.webopen(o, urls.append) self.failUnlessReallyEqual(rc, 0) self.failUnlessReallyEqual(len(urls), 1) self.failUnlessReallyEqual(urls[0], expected_url) @@ -2747,25 +2690,20 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): fileutil.make_dirs(basedir) nodeurl_path = os.path.join(basedir, 'node.url') fileutil.write(nodeurl_path, 'http://example.net:2357/') + def parse(args): return parse_options(basedir, "backup", args) # test simple exclude - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude', '*lyx', '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude', '*lyx', 'from', 'to']) filtered = list(backup_options.filter_listdir(root_listdir)) self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), (u'nice_doc.lyx',)) # multiple exclude - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude', '*lyx', '--exclude', 'lib.?', '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude', '*lyx', '--exclude', 'lib.?', 'from', 'to']) filtered = list(backup_options.filter_listdir(root_listdir)) self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'), (u'nice_doc.lyx', u'lib.a')) # vcs metadata exclusion - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude-vcs', '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude-vcs', 'from', 'to']) filtered = list(backup_options.filter_listdir(subdir_listdir)) self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'run_snake_run.py',), (u'CVS', u'.svn', u'_darcs')) @@ -2773,22 +2711,17 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): exclusion_string = "_darcs\n*py\n.svn" excl_filepath = os.path.join(basedir, 'exclusion') fileutil.write(excl_filepath, exclusion_string) - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude-from', excl_filepath, '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude-from', excl_filepath, 'from', 'to']) filtered = list(backup_options.filter_listdir(subdir_listdir)) self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'CVS'), (u'.svn', u'_darcs', u'run_snake_run.py')) # test BackupConfigurationError self.failUnlessRaises(cli.BackupConfigurationError, - backup_options.parseOptions, - ['--exclude-from', excl_filepath + '.no', '--node-directory', - basedir, 'from', 'to']) + parse, + ['--exclude-from', excl_filepath + '.no', 'from', 'to']) # test that an iterator works too - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude', '*lyx', '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude', '*lyx', 'from', 'to']) filtered = list(backup_options.filter_listdir(iter(root_listdir))) self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), (u'nice_doc.lyx',)) @@ -2805,18 +2738,15 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): fileutil.make_dirs(basedir) nodeurl_path = os.path.join(basedir, 'node.url') fileutil.write(nodeurl_path, 'http://example.net:2357/') + def parse(args): return parse_options(basedir, "backup", args) # test simple exclude - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude', doc_pattern_arg, '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude', doc_pattern_arg, 'from', 'to']) filtered = list(backup_options.filter_listdir(root_listdir)) self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), (nice_doc,)) # multiple exclude - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude', doc_pattern_arg, '--exclude', 'lib.?', '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude', doc_pattern_arg, '--exclude', 'lib.?', 'from', 'to']) filtered = list(backup_options.filter_listdir(root_listdir)) self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'), (nice_doc, u'lib.a')) @@ -2824,17 +2754,13 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): exclusion_string = doc_pattern_arg + "\nlib.?" excl_filepath = os.path.join(basedir, 'exclusion') fileutil.write(excl_filepath, exclusion_string) - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude-from', excl_filepath, '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude-from', excl_filepath, 'from', 'to']) filtered = list(backup_options.filter_listdir(root_listdir)) self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'), (nice_doc, u'lib.a')) # test that an iterator works too - backup_options = cli.BackupOptions() - backup_options.parseOptions(['--exclude', doc_pattern_arg, '--node-directory', - basedir, 'from', 'to']) + backup_options = parse(['--exclude', doc_pattern_arg, 'from', 'to']) filtered = list(backup_options.filter_listdir(iter(root_listdir))) self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), (nice_doc,)) @@ -2845,14 +2771,13 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): fileutil.make_dirs(basedir) nodeurl_path = os.path.join(basedir, 'node.url') fileutil.write(nodeurl_path, 'http://example.net:2357/') + def parse(args): return parse_options(basedir, "backup", args) # ensure that tilde expansion is performed on exclude-from argument exclude_file = u'~/.tahoe/excludes.dummy' - backup_options = cli.BackupOptions() mock.return_value = StringIO() - backup_options.parseOptions(['--exclude-from', unicode_to_argv(exclude_file), - '--node-directory', basedir, 'from', 'to']) + parse(['--exclude-from', unicode_to_argv(exclude_file), 'from', 'to']) self.failUnlessIn(((abspath_expanduser_unicode(exclude_file),), {}), mock.call_args_list) def test_ignore_symlinks(self): @@ -3765,3 +3690,111 @@ class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase): _cleanup(None) raise return d + +class Options(unittest.TestCase): + # this test case only looks at argument-processing and simple stuff. + + def parse(self, args, stdout=None): + o = runner.Options() + if stdout is not None: + o.stdout = stdout + o.parseOptions(args) + while hasattr(o, "subOptions"): + o = o.subOptions + return o + + def test_list(self): + fileutil.rm_dir("cli/test_options") + fileutil.make_dirs("cli/test_options") + fileutil.make_dirs("cli/test_options/private") + fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n") + filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16, + fingerprint="\x00"*32) + private_uri = uri.DirectoryURI(filenode_uri).to_string() + fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n") + def parse2(args): return parse_options("cli/test_options", "ls", args) + o = parse2([]) + self.failUnlessEqual(o['node-url'], "http://localhost:8080/") + self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri) + self.failUnlessEqual(o.where, u"") + + o = parse2(["--node-url", "http://example.org:8111/"]) + self.failUnlessEqual(o['node-url'], "http://example.org:8111/") + self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri) + self.failUnlessEqual(o.where, u"") + + o = parse2(["--dir-cap", "root"]) + self.failUnlessEqual(o['node-url'], "http://localhost:8080/") + self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root") + self.failUnlessEqual(o.where, u"") + + other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16, + fingerprint="\x11"*32) + other_uri = uri.DirectoryURI(other_filenode_uri).to_string() + o = parse2(["--dir-cap", other_uri]) + self.failUnlessEqual(o['node-url'], "http://localhost:8080/") + self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri) + self.failUnlessEqual(o.where, u"") + + o = parse2(["--dir-cap", other_uri, "subdir"]) + self.failUnlessEqual(o['node-url'], "http://localhost:8080/") + self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri) + self.failUnlessEqual(o.where, u"subdir") + + self.failUnlessRaises(usage.UsageError, parse2, + ["--node-url", "NOT-A-URL"]) + + o = parse2(["--node-url", "http://localhost:8080"]) + self.failUnlessEqual(o["node-url"], "http://localhost:8080/") + + o = parse2(["--node-url", "https://localhost/"]) + self.failUnlessEqual(o["node-url"], "https://localhost/") + + def test_version(self): + # "tahoe --version" dumps text to stdout and exits + stdout = StringIO() + self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout) + self.failUnlessIn("allmydata-tahoe", stdout.getvalue()) + # but "tahoe SUBCOMMAND --version" should be rejected + self.failUnlessRaises(usage.UsageError, self.parse, + ["start", "--version"]) + self.failUnlessRaises(usage.UsageError, self.parse, + ["start", "--version-and-path"]) + + def test_quiet(self): + # accepted as an overall option, but not on subcommands + o = self.parse(["--quiet", "start"]) + self.failUnless(o.parent["quiet"]) + self.failUnlessRaises(usage.UsageError, self.parse, + ["start", "--quiet"]) + + def test_basedir(self): + # accept a --node-directory option before the verb, or a --basedir + # option after, or a basedir argument after, but none in the wrong + # place, and not more than one of the three. + o = self.parse(["start"]) + self.failUnlessEqual(o["basedir"], os.path.expanduser("~/.tahoe")) + o = self.parse(["start", "here"]) + self.failUnlessEqual(o["basedir"], os.path.abspath("here")) + o = self.parse(["start", "--basedir", "there"]) + self.failUnlessEqual(o["basedir"], os.path.abspath("there")) + o = self.parse(["--node-directory", "there", "start"]) + self.failUnlessEqual(o["basedir"], os.path.abspath("there")) + + self.failUnlessRaises(usage.UsageError, self.parse, + ["--basedir", "there", "start"]) + self.failUnlessRaises(usage.UsageError, self.parse, + ["start", "--node-directory", "there"]) + + self.failUnlessRaises(usage.UsageError, self.parse, + ["--node-directory=there", + "start", "--basedir=here"]) + self.failUnlessRaises(usage.UsageError, self.parse, + ["start", "--basedir=here", "anywhere"]) + self.failUnlessRaises(usage.UsageError, self.parse, + ["--node-directory=there", + "start", "anywhere"]) + self.failUnlessRaises(usage.UsageError, self.parse, + ["--node-directory=there", + "start", "--basedir=here", "anywhere"]) + diff --git a/src/allmydata/test/test_deepcheck.py b/src/allmydata/test/test_deepcheck.py index 045f5c0e..98f2a528 100644 --- a/src/allmydata/test/test_deepcheck.py +++ b/src/allmydata/test/test_deepcheck.py @@ -759,8 +759,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_manifest_stream1(self): basedir = self.get_clientdir(0) - d = self._run_cli(["manifest", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "manifest", self.root_uri]) def _check((out,err)): self.failUnlessEqual(err, "") @@ -787,8 +787,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_manifest_stream2(self): basedir = self.get_clientdir(0) - d = self._run_cli(["manifest", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "manifest", "--raw", self.root_uri]) def _check((out,err)): @@ -800,8 +800,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_manifest_stream3(self): basedir = self.get_clientdir(0) - d = self._run_cli(["manifest", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "manifest", "--storage-index", self.root_uri]) def _check((out,err)): @@ -812,8 +812,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_manifest_stream4(self): basedir = self.get_clientdir(0) - d = self._run_cli(["manifest", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "manifest", "--verify-cap", self.root_uri]) def _check((out,err)): @@ -828,8 +828,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_manifest_stream5(self): basedir = self.get_clientdir(0) - d = self._run_cli(["manifest", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "manifest", "--repair-cap", self.root_uri]) def _check((out,err)): @@ -844,8 +844,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_stats1(self): basedir = self.get_clientdir(0) - d = self._run_cli(["stats", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "stats", self.root_uri]) def _check3((out,err)): lines = [l.strip() for l in out.split("\n") if l] @@ -864,8 +864,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): def do_cli_stats2(self): basedir = self.get_clientdir(0) - d = self._run_cli(["stats", - "--node-directory", basedir, + d = self._run_cli(["--node-directory", basedir, + "stats", "--raw", self.root_uri]) def _check4((out,err)): diff --git a/src/allmydata/test/test_runner.py b/src/allmydata/test/test_runner.py index aed220e4..33974e2e 100644 --- a/src/allmydata/test/test_runner.py +++ b/src/allmydata/test/test_runner.py @@ -284,7 +284,7 @@ class CreateNode(unittest.TestCase): # test the --node-directory form n3 = os.path.join(basedir, command + "-n3") - argv = ["--quiet", command, "--node-directory", n3] + argv = ["--quiet", "--node-directory", n3, command] rc, out, err = self.run_tahoe(argv) self.failUnlessEqual(err, "") self.failUnlessEqual(out, "") diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 6ef94b30..472f106d 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -1440,7 +1440,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase): def run(ignored, verb, *args, **kwargs): stdin = kwargs.get("stdin", "") - newargs = [verb] + nodeargs + list(args) + newargs = nodeargs + [verb] + list(args) return self._run_cli(newargs, stdin=stdin) def _check_ls((out,err), expected_children, unexpected_children=[]): @@ -1745,7 +1745,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase): # tahoe_ls doesn't currently handle the error correctly: it tries to # JSON-parse a traceback. ## def _ls_missing(res): -## argv = ["ls"] + nodeargs + ["bogus"] +## argv = nodeargs + ["ls", "bogus"] ## return self._run_cli(argv) ## d.addCallback(_ls_missing) ## def _check_ls_missing((out,err)): @@ -1769,7 +1769,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase): def _run_in_subprocess(ignored, verb, *args, **kwargs): stdin = kwargs.get("stdin") env = kwargs.get("env") - newargs = [verb, "--node-directory", self.getdir("client0")] + list(args) + newargs = ["--node-directory", self.getdir("client0"), verb] + list(args) return self.run_bintahoe(newargs, stdin=stdin, env=env) def _check_succeeded(res, check_stderr=True):