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
This commit is contained in:
Brian Warner
2012-06-18 10:43:49 -07:00
parent 643eb4f372
commit 5874a7d3a7
13 changed files with 250 additions and 212 deletions

View File

@ -1,12 +1,13 @@
from twisted.python import usage from twisted.python import usage
from allmydata.scripts.common import BaseOptions
class GenerateKeypairOptions(usage.Options): class GenerateKeypairOptions(BaseOptions):
def getSynopsis(self): def getSynopsis(self):
return "Usage: tahoe admin generate-keypair" return "Usage: tahoe admin generate-keypair"
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Generate a public/private keypair, dumped to stdout as two lines of ASCII.. 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, "private:", privkey_vs
print >>out, "public:", pubkey_vs print >>out, "public:", pubkey_vs
class DerivePubkeyOptions(usage.Options): class DerivePubkeyOptions(BaseOptions):
def parseArgs(self, privkey): def parseArgs(self, privkey):
self.privkey = privkey self.privkey = privkey
@ -28,7 +29,7 @@ class DerivePubkeyOptions(usage.Options):
return "Usage: tahoe admin derive-pubkey PRIVKEY" return "Usage: tahoe admin derive-pubkey PRIVKEY"
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Given a private (signing) key that was previously generated with Given a private (signing) key that was previously generated with
generate-keypair, derive the public key and print it to stdout. generate-keypair, derive the public key and print it to stdout.
@ -45,7 +46,7 @@ def derive_pubkey(options):
print >>out, "public:", pubkey_vs print >>out, "public:", pubkey_vs
return 0 return 0
class AdminCommand(usage.Options): class AdminCommand(BaseOptions):
subCommands = [ subCommands = [
("generate-keypair", None, GenerateKeypairOptions, ("generate-keypair", None, GenerateKeypairOptions,
"Generate a public/private keypair, write to stdout."), "Generate a public/private keypair, write to stdout."),
@ -58,7 +59,7 @@ class AdminCommand(usage.Options):
def getSynopsis(self): def getSynopsis(self):
return "Usage: tahoe admin SUBCOMMAND" return "Usage: tahoe admin SUBCOMMAND"
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Please run e.g. 'tahoe admin generate-keypair --help' for more details on Please run e.g. 'tahoe admin generate-keypair --help' for more details on
each subcommand. each subcommand.

View File

@ -1,6 +1,7 @@
import os.path, re, fnmatch import os.path, re, fnmatch
from twisted.python import usage 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 from allmydata.util.encodingutil import argv_to_unicode, argv_to_abspath, quote_output
NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?") NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?")
@ -9,23 +10,18 @@ _default_nodedir = get_default_nodedir()
class VDriveOptions(BaseOptions): class VDriveOptions(BaseOptions):
optParameters = [ 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, ["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 ."], "This overrides the URL found in the --node-directory ."],
["dir-cap", None, None, ["dir-cap", None, None,
"Specify which dirnode URI should be used as the 'tahoe' alias."] "Specify which dirnode URI should be used as the 'tahoe' alias."]
] ]
def postOptions(self): def postOptions(self):
if self['node-directory']: self["quiet"] = self.parent["quiet"]
self['node-directory'] = argv_to_abspath(self['node-directory']) if self.parent['node-directory']:
self['node-directory'] = argv_to_abspath(self.parent['node-directory'])
else: else:
self['node-directory'] = _default_nodedir self['node-directory'] = _default_nodedir

View File

@ -25,58 +25,42 @@ def get_default_nodedir():
class BaseOptions(usage.Options): 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): def __init__(self):
super(BaseOptions, self).__init__() super(BaseOptions, self).__init__()
self.command_name = os.path.basename(sys.argv[0]) self.command_name = os.path.basename(sys.argv[0])
if self.command_name == 'trial': if self.command_name == 'trial':
self.command_name = 'tahoe' self.command_name = 'tahoe'
# Only allow "tahoe --version", not e.g. "tahoe start --version"
def opt_version(self): def opt_version(self):
import allmydata raise usage.UsageError("--version not allowed on subcommands")
print >>self.stdout, allmydata.get_package_versions_string(debug=True)
self.no_command_needed = True
def opt_version_and_path(self): class BasedirOptions(BaseOptions):
import allmydata
print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True)
self.no_command_needed = True
class BasedirMixin:
default_nodedir = _default_nodedir default_nodedir = _default_nodedir
optParameters = [ 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): def parseArgs(self, basedir=None):
if self['node-directory'] and self['basedir']: if self.parent['node-directory'] and self['basedir']:
raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) " raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) options cannot both be used.")
"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: if basedir:
b = argv_to_abspath(basedir) b = argv_to_abspath(basedir)
elif self['basedir']: elif self['basedir']:
b = argv_to_abspath(self['basedir']) b = argv_to_abspath(self['basedir'])
elif self['node-directory']: elif self.parent['node-directory']:
b = argv_to_abspath(self['node-directory']) b = argv_to_abspath(self.parent['node-directory'])
else: elif self.default_nodedir:
b = 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 self['basedir'] = b
def postOptions(self): def postOptions(self):

View File

@ -1,11 +1,11 @@
import os, sys 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.assertutil import precondition
from allmydata.util.encodingutil import listdir_unicode, argv_to_unicode, quote_output from allmydata.util.encodingutil import listdir_unicode, argv_to_unicode, quote_output
import allmydata import allmydata
class CreateClientOptions(BasedirMixin, BaseOptions): class CreateClientOptions(BasedirOptions):
optParameters = [ optParameters = [
# we provide 'create-node'-time options for the most common # we provide 'create-node'-time options for the most common
# configuration knobs. The rest can be controlled by editing # 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,) return "Usage: %s create-node [options] [NODEDIR]" % (self.command_name,)
class CreateIntroducerOptions(BasedirMixin, BaseOptions): class CreateIntroducerOptions(BasedirOptions):
default_nodedir = None default_nodedir = None
optParameters = [
["node-directory", "d", None, "Specify which directory the introducer should be created in. [no default]"],
]
def getSynopsis(self): def getSynopsis(self):
return "Usage: %s create-introducer [options] NODEDIR" % (self.command_name,) return "Usage: %s create-introducer [options] NODEDIR" % (self.command_name,)

View File

@ -6,9 +6,10 @@ from twisted.python import usage, failure
from twisted.internet import defer from twisted.internet import defer
from twisted.scripts import trial as twisted_trial from twisted.scripts import trial as twisted_trial
from foolscap.logging import cli as foolscap_cli from foolscap.logging import cli as foolscap_cli
from allmydata.scripts.common import BaseOptions
class DumpOptions(usage.Options): class DumpOptions(BaseOptions):
def getSynopsis(self): def getSynopsis(self):
return "Usage: tahoe debug dump-share SHARE_FILENAME" return "Usage: tahoe debug dump-share SHARE_FILENAME"
@ -18,7 +19,7 @@ class DumpOptions(usage.Options):
] ]
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Print lots of information about the given share, by parsing the share's Print lots of information about the given share, by parsing the share's
contents. This includes share type, lease information, encoding parameters, 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): def getSynopsis(self):
return "Usage: tahoe debug dump-cap [options] FILECAP" return "Usage: tahoe debug dump-cap [options] FILECAP"
optParameters = [ optParameters = [
@ -420,7 +421,7 @@ class DumpCapOptions(usage.Options):
self.cap = cap self.cap = cap
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Print information about the given cap-string (aka: URI, file-cap, dir-cap, 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 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: else:
print >>out, "unknown cap type" print >>out, "unknown cap type"
class FindSharesOptions(usage.Options): class FindSharesOptions(BaseOptions):
def getSynopsis(self): def getSynopsis(self):
return "Usage: tahoe debug find-shares STORAGE_INDEX NODEDIRS.." return "Usage: tahoe debug find-shares STORAGE_INDEX NODEDIRS.."
@ -617,7 +618,7 @@ class FindSharesOptions(usage.Options):
self.nodedirs = map(argv_to_abspath, nodedirs) self.nodedirs = map(argv_to_abspath, nodedirs)
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Locate all shares for the given storage index. This command looks through one 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, or more node directories to find the shares. It returns a list of filenames,
@ -657,7 +658,7 @@ def find_shares(options):
return 0 return 0
class CatalogSharesOptions(usage.Options): class CatalogSharesOptions(BaseOptions):
""" """
""" """
@ -671,7 +672,7 @@ class CatalogSharesOptions(usage.Options):
return "Usage: tahoe debug catalog-shares NODEDIRS.." return "Usage: tahoe debug catalog-shares NODEDIRS.."
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Locate all shares in the given node directories, and emit a one-line summary Locate all shares in the given node directories, and emit a one-line summary
of each share. Run it like this: 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) print >>err, "Error processing %s" % quote_output(si_dir)
failure.Failure().printTraceback(err) failure.Failure().printTraceback(err)
class CorruptShareOptions(usage.Options): class CorruptShareOptions(BaseOptions):
def getSynopsis(self): def getSynopsis(self):
return "Usage: tahoe debug corrupt-share SHARE_FILENAME" return "Usage: tahoe debug corrupt-share SHARE_FILENAME"
@ -888,7 +889,7 @@ class CorruptShareOptions(usage.Options):
] ]
def getUsage(self, width=None): def getUsage(self, width=None):
t = usage.Options.getUsage(self, width) t = BaseOptions.getUsage(self, width)
t += """ t += """
Corrupt the given share by flipping a bit. This will cause a Corrupt the given share by flipping a bit. This will cause a
verifying/downloading client to log an integrity-check failure incident, and 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): def getSynopsis(self):
return "Usage: tahoe debug repl" return "Usage: tahoe debug repl"
@ -1042,7 +1043,7 @@ def flogtool(config):
return foolscap_cli.run_flogtool() return foolscap_cli.run_flogtool()
class DebugCommand(usage.Options): class DebugCommand(BaseOptions):
subCommands = [ subCommands = [
["dump-share", None, DumpOptions, ["dump-share", None, DumpOptions,
"Unpack and display the contents of a share (uri_extension and leases)."], "Unpack and display the contents of a share (uri_extension and leases)."],
@ -1060,7 +1061,7 @@ class DebugCommand(usage.Options):
def getSynopsis(self): def getSynopsis(self):
return "" return ""
def getUsage(self, width=None): def getUsage(self, width=None):
#t = usage.Options.getUsage(self, width) #t = BaseOptions.getUsage(self, width)
t = """Usage: tahoe debug SUBCOMMAND t = """Usage: tahoe debug SUBCOMMAND
Subcommands: Subcommands:
tahoe debug dump-share Unpack and display the contents of a share. tahoe debug dump-share Unpack and display the contents of a share.

View File

@ -1,16 +1,12 @@
import os, sys 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.assertutil import precondition
from allmydata.util.encodingutil import listdir_unicode, quote_output from allmydata.util.encodingutil import listdir_unicode, quote_output
class CreateKeyGeneratorOptions(BasedirMixin, BaseOptions): class CreateKeyGeneratorOptions(BasedirOptions):
default_nodedir = None default_nodedir = None
optParameters = [
["node-directory", "d", None, "Specify which directory the key-generator should be created in. [no default]"],
]
def getSynopsis(self): def getSynopsis(self):
return "Usage: %s create-key-generator [options] NODEDIR" % (self.command_name,) return "Usage: %s create-key-generator [options] NODEDIR" % (self.command_name,)

View File

@ -1,10 +1,10 @@
import sys import os, sys
from cStringIO import StringIO from cStringIO import StringIO
from twisted.python import usage 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.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin
from allmydata.util.encodingutil import quote_output, get_io_encoding from allmydata.util.encodingutil import quote_output, get_io_encoding
@ -15,7 +15,24 @@ def GROUP(s):
return [("\n" + s, None, None, None)] 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> [command options]" synopsis = "\nUsage: tahoe <command> [command options]"
subCommands = ( GROUP("Administration") subCommands = ( GROUP("Administration")
+ create_node.subCommands + create_node.subCommands
@ -30,6 +47,28 @@ class Options(BaseOptions, usage.Options):
+ cli.subCommands + 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> [command-options]"
def getUsage(self, **kwargs): def getUsage(self, **kwargs):
t = usage.Options.getUsage(self, **kwargs) t = usage.Options.getUsage(self, **kwargs)
return t + "\nPlease run 'tahoe <command> --help' for more details on each command.\n" return t + "\nPlease run 'tahoe <command> --help' for more details on each command.\n"

View File

@ -1,12 +1,12 @@
import os, sys, signal, time 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 import fileutil
from allmydata.util.assertutil import precondition from allmydata.util.assertutil import precondition
from allmydata.util.encodingutil import listdir_unicode, quote_output from allmydata.util.encodingutil import listdir_unicode, quote_output
class StartOptions(BasedirMixin, BaseOptions): class StartOptions(BasedirOptions):
optFlags = [ optFlags = [
["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."], ["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."], ["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,) return "Usage: %s start [options] [NODEDIR]" % (self.command_name,)
class StopOptions(BasedirMixin, BaseOptions): class StopOptions(BasedirOptions):
def getSynopsis(self): def getSynopsis(self):
return "Usage: %s stop [options] [NODEDIR]" % (self.command_name,) return "Usage: %s stop [options] [NODEDIR]" % (self.command_name,)
class RestartOptions(BasedirMixin, BaseOptions): class RestartOptions(BasedirOptions):
optFlags = [ optFlags = [
["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."], ["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."], ["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,) return "Usage: %s restart [options] [NODEDIR]" % (self.command_name,)
class RunOptions(BasedirMixin, BaseOptions): class RunOptions(BasedirOptions):
default_nodedir = u"." 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): def getSynopsis(self):
return "Usage: %s run [options] [NODEDIR]" % (self.command_name,) return "Usage: %s run [options] [NODEDIR]" % (self.command_name,)

View File

@ -1,16 +1,12 @@
import os, sys 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.assertutil import precondition
from allmydata.util.encodingutil import listdir_unicode, quote_output from allmydata.util.encodingutil import listdir_unicode, quote_output
class CreateStatsGathererOptions(BasedirMixin, BaseOptions): class CreateStatsGathererOptions(BasedirOptions):
default_nodedir = None default_nodedir = None
optParameters = [
["node-directory", "d", None, "Specify which directory the stats-gatherer should be created in. [no default]"],
]
def getSynopsis(self): def getSynopsis(self):
return "Usage: %s create-stats-gatherer [options] NODEDIR" % (self.command_name,) return "Usage: %s create-stats-gatherer [options] NODEDIR" % (self.command_name,)

View File

@ -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 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): class CLITestMixin(ReallyEqualMixin):
def do_cli(self, verb, *args, **kwargs): def do_cli(self, verb, *args, **kwargs):
nodeargs = [ nodeargs = [
"--node-directory", self.get_clientdir(), "--node-directory", self.get_clientdir(),
] ]
argv = [verb] + nodeargs + list(args) argv = nodeargs + [verb] + list(args)
stdin = kwargs.get("stdin", "") stdin = kwargs.get("stdin", "")
stdout, stderr = StringIO(), StringIO() stdout, stderr = StringIO(), StringIO()
d = threads.deferToThread(runner.runner, argv, run_by_human=False, d = threads.deferToThread(runner.runner, argv, run_by_human=False,
@ -73,69 +79,6 @@ class CLITestMixin(ReallyEqualMixin):
class CLI(CLITestMixin, unittest.TestCase): 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): def _dump_cap(self, *args):
config = debug.DumpCapOptions() config = debug.DumpCapOptions()
config.stdout,config.stderr = StringIO(), StringIO() config.stdout,config.stderr = StringIO(), StringIO()
@ -730,11 +673,11 @@ class Help(unittest.TestCase):
class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase): class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase):
def _test_webopen(self, args, expected_url): def _test_webopen(self, args, expected_url):
woo = cli.WebopenOptions() o = runner.Options()
all_args = ["--node-directory", self.get_clientdir()] + list(args) o.parseOptions(["--node-directory", self.get_clientdir(), "webopen"]
woo.parseOptions(all_args) + list(args))
urls = [] urls = []
rc = cli.webopen(woo, urls.append) rc = cli.webopen(o, urls.append)
self.failUnlessReallyEqual(rc, 0) self.failUnlessReallyEqual(rc, 0)
self.failUnlessReallyEqual(len(urls), 1) self.failUnlessReallyEqual(len(urls), 1)
self.failUnlessReallyEqual(urls[0], expected_url) self.failUnlessReallyEqual(urls[0], expected_url)
@ -2747,25 +2690,20 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
fileutil.make_dirs(basedir) fileutil.make_dirs(basedir)
nodeurl_path = os.path.join(basedir, 'node.url') nodeurl_path = os.path.join(basedir, 'node.url')
fileutil.write(nodeurl_path, 'http://example.net:2357/') fileutil.write(nodeurl_path, 'http://example.net:2357/')
def parse(args): return parse_options(basedir, "backup", args)
# test simple exclude # test simple exclude
backup_options = cli.BackupOptions() backup_options = parse(['--exclude', '*lyx', 'from', 'to'])
backup_options.parseOptions(['--exclude', '*lyx', '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(root_listdir)) filtered = list(backup_options.filter_listdir(root_listdir))
self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'),
(u'nice_doc.lyx',)) (u'nice_doc.lyx',))
# multiple exclude # multiple exclude
backup_options = cli.BackupOptions() backup_options = parse(['--exclude', '*lyx', '--exclude', 'lib.?', 'from', 'to'])
backup_options.parseOptions(['--exclude', '*lyx', '--exclude', 'lib.?', '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(root_listdir)) filtered = list(backup_options.filter_listdir(root_listdir))
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
(u'nice_doc.lyx', u'lib.a')) (u'nice_doc.lyx', u'lib.a'))
# vcs metadata exclusion # vcs metadata exclusion
backup_options = cli.BackupOptions() backup_options = parse(['--exclude-vcs', 'from', 'to'])
backup_options.parseOptions(['--exclude-vcs', '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(subdir_listdir)) filtered = list(backup_options.filter_listdir(subdir_listdir))
self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'run_snake_run.py',), self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'run_snake_run.py',),
(u'CVS', u'.svn', u'_darcs')) (u'CVS', u'.svn', u'_darcs'))
@ -2773,22 +2711,17 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
exclusion_string = "_darcs\n*py\n.svn" exclusion_string = "_darcs\n*py\n.svn"
excl_filepath = os.path.join(basedir, 'exclusion') excl_filepath = os.path.join(basedir, 'exclusion')
fileutil.write(excl_filepath, exclusion_string) fileutil.write(excl_filepath, exclusion_string)
backup_options = cli.BackupOptions() backup_options = parse(['--exclude-from', excl_filepath, 'from', 'to'])
backup_options.parseOptions(['--exclude-from', excl_filepath, '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(subdir_listdir)) filtered = list(backup_options.filter_listdir(subdir_listdir))
self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'CVS'), self._check_filtering(filtered, subdir_listdir, (u'another_doc.lyx', u'CVS'),
(u'.svn', u'_darcs', u'run_snake_run.py')) (u'.svn', u'_darcs', u'run_snake_run.py'))
# test BackupConfigurationError # test BackupConfigurationError
self.failUnlessRaises(cli.BackupConfigurationError, self.failUnlessRaises(cli.BackupConfigurationError,
backup_options.parseOptions, parse,
['--exclude-from', excl_filepath + '.no', '--node-directory', ['--exclude-from', excl_filepath + '.no', 'from', 'to'])
basedir, 'from', 'to'])
# test that an iterator works too # test that an iterator works too
backup_options = cli.BackupOptions() backup_options = parse(['--exclude', '*lyx', 'from', 'to'])
backup_options.parseOptions(['--exclude', '*lyx', '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(iter(root_listdir))) filtered = list(backup_options.filter_listdir(iter(root_listdir)))
self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'),
(u'nice_doc.lyx',)) (u'nice_doc.lyx',))
@ -2805,18 +2738,15 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
fileutil.make_dirs(basedir) fileutil.make_dirs(basedir)
nodeurl_path = os.path.join(basedir, 'node.url') nodeurl_path = os.path.join(basedir, 'node.url')
fileutil.write(nodeurl_path, 'http://example.net:2357/') fileutil.write(nodeurl_path, 'http://example.net:2357/')
def parse(args): return parse_options(basedir, "backup", args)
# test simple exclude # test simple exclude
backup_options = cli.BackupOptions() backup_options = parse(['--exclude', doc_pattern_arg, 'from', 'to'])
backup_options.parseOptions(['--exclude', doc_pattern_arg, '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(root_listdir)) filtered = list(backup_options.filter_listdir(root_listdir))
self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'),
(nice_doc,)) (nice_doc,))
# multiple exclude # multiple exclude
backup_options = cli.BackupOptions() backup_options = parse(['--exclude', doc_pattern_arg, '--exclude', 'lib.?', 'from', 'to'])
backup_options.parseOptions(['--exclude', doc_pattern_arg, '--exclude', 'lib.?', '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(root_listdir)) filtered = list(backup_options.filter_listdir(root_listdir))
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
(nice_doc, u'lib.a')) (nice_doc, u'lib.a'))
@ -2824,17 +2754,13 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
exclusion_string = doc_pattern_arg + "\nlib.?" exclusion_string = doc_pattern_arg + "\nlib.?"
excl_filepath = os.path.join(basedir, 'exclusion') excl_filepath = os.path.join(basedir, 'exclusion')
fileutil.write(excl_filepath, exclusion_string) fileutil.write(excl_filepath, exclusion_string)
backup_options = cli.BackupOptions() backup_options = parse(['--exclude-from', excl_filepath, 'from', 'to'])
backup_options.parseOptions(['--exclude-from', excl_filepath, '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(root_listdir)) filtered = list(backup_options.filter_listdir(root_listdir))
self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'_darcs', u'subdir'),
(nice_doc, u'lib.a')) (nice_doc, u'lib.a'))
# test that an iterator works too # test that an iterator works too
backup_options = cli.BackupOptions() backup_options = parse(['--exclude', doc_pattern_arg, 'from', 'to'])
backup_options.parseOptions(['--exclude', doc_pattern_arg, '--node-directory',
basedir, 'from', 'to'])
filtered = list(backup_options.filter_listdir(iter(root_listdir))) filtered = list(backup_options.filter_listdir(iter(root_listdir)))
self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'), self._check_filtering(filtered, root_listdir, (u'lib.a', u'_darcs', u'subdir'),
(nice_doc,)) (nice_doc,))
@ -2845,14 +2771,13 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
fileutil.make_dirs(basedir) fileutil.make_dirs(basedir)
nodeurl_path = os.path.join(basedir, 'node.url') nodeurl_path = os.path.join(basedir, 'node.url')
fileutil.write(nodeurl_path, 'http://example.net:2357/') 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 # ensure that tilde expansion is performed on exclude-from argument
exclude_file = u'~/.tahoe/excludes.dummy' exclude_file = u'~/.tahoe/excludes.dummy'
backup_options = cli.BackupOptions()
mock.return_value = StringIO() mock.return_value = StringIO()
backup_options.parseOptions(['--exclude-from', unicode_to_argv(exclude_file), parse(['--exclude-from', unicode_to_argv(exclude_file), 'from', 'to'])
'--node-directory', basedir, 'from', 'to'])
self.failUnlessIn(((abspath_expanduser_unicode(exclude_file),), {}), mock.call_args_list) self.failUnlessIn(((abspath_expanduser_unicode(exclude_file),), {}), mock.call_args_list)
def test_ignore_symlinks(self): def test_ignore_symlinks(self):
@ -3765,3 +3690,111 @@ class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase):
_cleanup(None) _cleanup(None)
raise raise
return d 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"])

View File

@ -759,8 +759,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
def do_cli_manifest_stream1(self): def do_cli_manifest_stream1(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["manifest", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "manifest",
self.root_uri]) self.root_uri])
def _check((out,err)): def _check((out,err)):
self.failUnlessEqual(err, "") self.failUnlessEqual(err, "")
@ -787,8 +787,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
def do_cli_manifest_stream2(self): def do_cli_manifest_stream2(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["manifest", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "manifest",
"--raw", "--raw",
self.root_uri]) self.root_uri])
def _check((out,err)): def _check((out,err)):
@ -800,8 +800,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
def do_cli_manifest_stream3(self): def do_cli_manifest_stream3(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["manifest", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "manifest",
"--storage-index", "--storage-index",
self.root_uri]) self.root_uri])
def _check((out,err)): def _check((out,err)):
@ -812,8 +812,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
def do_cli_manifest_stream4(self): def do_cli_manifest_stream4(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["manifest", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "manifest",
"--verify-cap", "--verify-cap",
self.root_uri]) self.root_uri])
def _check((out,err)): def _check((out,err)):
@ -828,8 +828,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
def do_cli_manifest_stream5(self): def do_cli_manifest_stream5(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["manifest", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "manifest",
"--repair-cap", "--repair-cap",
self.root_uri]) self.root_uri])
def _check((out,err)): def _check((out,err)):
@ -844,8 +844,8 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
def do_cli_stats1(self): def do_cli_stats1(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["stats", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "stats",
self.root_uri]) self.root_uri])
def _check3((out,err)): def _check3((out,err)):
lines = [l.strip() for l in out.split("\n") if l] 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): def do_cli_stats2(self):
basedir = self.get_clientdir(0) basedir = self.get_clientdir(0)
d = self._run_cli(["stats", d = self._run_cli(["--node-directory", basedir,
"--node-directory", basedir, "stats",
"--raw", "--raw",
self.root_uri]) self.root_uri])
def _check4((out,err)): def _check4((out,err)):

View File

@ -284,7 +284,7 @@ class CreateNode(unittest.TestCase):
# test the --node-directory form # test the --node-directory form
n3 = os.path.join(basedir, command + "-n3") 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) rc, out, err = self.run_tahoe(argv)
self.failUnlessEqual(err, "") self.failUnlessEqual(err, "")
self.failUnlessEqual(out, "") self.failUnlessEqual(out, "")

View File

@ -1440,7 +1440,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
def run(ignored, verb, *args, **kwargs): def run(ignored, verb, *args, **kwargs):
stdin = kwargs.get("stdin", "") stdin = kwargs.get("stdin", "")
newargs = [verb] + nodeargs + list(args) newargs = nodeargs + [verb] + list(args)
return self._run_cli(newargs, stdin=stdin) return self._run_cli(newargs, stdin=stdin)
def _check_ls((out,err), expected_children, unexpected_children=[]): 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 # tahoe_ls doesn't currently handle the error correctly: it tries to
# JSON-parse a traceback. # JSON-parse a traceback.
## def _ls_missing(res): ## def _ls_missing(res):
## argv = ["ls"] + nodeargs + ["bogus"] ## argv = nodeargs + ["ls", "bogus"]
## return self._run_cli(argv) ## return self._run_cli(argv)
## d.addCallback(_ls_missing) ## d.addCallback(_ls_missing)
## def _check_ls_missing((out,err)): ## def _check_ls_missing((out,err)):
@ -1769,7 +1769,7 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
def _run_in_subprocess(ignored, verb, *args, **kwargs): def _run_in_subprocess(ignored, verb, *args, **kwargs):
stdin = kwargs.get("stdin") stdin = kwargs.get("stdin")
env = kwargs.get("env") 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) return self.run_bintahoe(newargs, stdin=stdin, env=env)
def _check_succeeded(res, check_stderr=True): def _check_succeeded(res, check_stderr=True):