3 from cStringIO import StringIO
6 from twisted.trial import unittest
7 from twisted.python.monkey import MonkeyPatcher
10 from allmydata.util import fileutil, hashutil, base32, keyutil
11 from allmydata.util.namespace import Namespace
12 from allmydata import uri
13 from allmydata.immutable import upload
14 from allmydata.dirnode import normalize
15 from allmydata.scripts.common_http import socket_error
16 import allmydata.scripts.common_http
17 from pycryptopp.publickey import ed25519
19 # Test that the scripts can be imported.
20 from allmydata.scripts import create_node, debug, keygen, startstop_node, \
21 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls, \
22 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen
23 _hush_pyflakes = [create_node, debug, keygen, startstop_node,
24 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls,
25 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen]
27 from allmydata.scripts import common
28 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases, get_alias, \
31 from allmydata.scripts import cli, debug, runner
32 from allmydata.test.common_util import ReallyEqualMixin
33 from allmydata.test.no_network import GridTestMixin
34 from twisted.internet import threads # CLI tests use deferToThread
35 from twisted.python import usage
37 from allmydata.util.assertutil import precondition
38 from allmydata.util.encodingutil import listdir_unicode, unicode_platform, \
39 get_io_encoding, get_filesystem_encoding
41 timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s
43 def parse_options(basedir, command, args):
45 o.parseOptions(["--node-directory", basedir, command] + args)
46 while hasattr(o, "subOptions"):
50 class CLITestMixin(ReallyEqualMixin):
51 def do_cli(self, verb, *args, **kwargs):
52 # client_num is used to execute client CLI commands on a specific client.
53 client_num = kwargs.get("client_num", 0)
56 "--node-directory", self.get_clientdir(i=client_num),
58 argv = nodeargs + [verb] + list(args)
59 stdin = kwargs.get("stdin", "")
60 stdout, stderr = StringIO(), StringIO()
61 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
62 stdin=StringIO(stdin),
63 stdout=stdout, stderr=stderr)
65 return rc, stdout.getvalue(), stderr.getvalue()
69 def skip_if_cannot_represent_filename(self, u):
70 precondition(isinstance(u, unicode))
72 enc = get_filesystem_encoding()
73 if not unicode_platform():
76 except UnicodeEncodeError:
77 raise unittest.SkipTest("A non-ASCII filename could not be encoded on this platform.")
80 class CLI(CLITestMixin, unittest.TestCase):
81 def _dump_cap(self, *args):
82 config = debug.DumpCapOptions()
83 config.stdout,config.stderr = StringIO(), StringIO()
84 config.parseOptions(args)
85 debug.dump_cap(config)
86 self.failIf(config.stderr.getvalue())
87 output = config.stdout.getvalue()
90 def test_dump_cap_chk(self):
91 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
92 uri_extension_hash = hashutil.uri_extension_hash("stuff")
96 u = uri.CHKFileURI(key=key,
97 uri_extension_hash=uri_extension_hash,
98 needed_shares=needed_shares,
99 total_shares=total_shares,
101 output = self._dump_cap(u.to_string())
102 self.failUnless("CHK File:" in output, output)
103 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
104 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
105 self.failUnless("size: 1234" in output, output)
106 self.failUnless("k/N: 25/100" in output, output)
107 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
109 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
111 self.failUnless("client renewal secret: znxmki5zdibb5qlt46xbdvk2t55j7hibejq3i5ijyurkr6m6jkhq" in output, output)
113 output = self._dump_cap(u.get_verify_cap().to_string())
114 self.failIf("key: " in output, output)
115 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
116 self.failUnless("size: 1234" in output, output)
117 self.failUnless("k/N: 25/100" in output, output)
118 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
120 prefixed_u = "http://127.0.0.1/uri/%s" % urllib.quote(u.to_string())
121 output = self._dump_cap(prefixed_u)
122 self.failUnless("CHK File:" in output, output)
123 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
124 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
125 self.failUnless("size: 1234" in output, output)
126 self.failUnless("k/N: 25/100" in output, output)
127 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
129 def test_dump_cap_lit(self):
130 u = uri.LiteralFileURI("this is some data")
131 output = self._dump_cap(u.to_string())
132 self.failUnless("Literal File URI:" in output, output)
133 self.failUnless("data: 'this is some data'" in output, output)
135 def test_dump_cap_sdmf(self):
136 writekey = "\x01" * 16
137 fingerprint = "\xfe" * 32
138 u = uri.WriteableSSKFileURI(writekey, fingerprint)
140 output = self._dump_cap(u.to_string())
141 self.failUnless("SDMF Writeable URI:" in output, output)
142 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
143 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
144 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
145 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
147 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
149 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
151 fileutil.make_dirs("cli/test_dump_cap/private")
152 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
153 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
155 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
157 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
159 self.failIf("file renewal secret:" in output, output)
161 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
163 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
164 self.failIf("file renewal secret:" in output, output)
166 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
167 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
169 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
170 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
171 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
174 output = self._dump_cap(u.to_string())
175 self.failUnless("SDMF Read-only URI:" in output, output)
176 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
177 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
178 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
180 u = u.get_verify_cap()
181 output = self._dump_cap(u.to_string())
182 self.failUnless("SDMF Verifier URI:" in output, output)
183 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
184 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
186 def test_dump_cap_mdmf(self):
187 writekey = "\x01" * 16
188 fingerprint = "\xfe" * 32
189 u = uri.WriteableMDMFFileURI(writekey, fingerprint)
191 output = self._dump_cap(u.to_string())
192 self.failUnless("MDMF Writeable URI:" in output, output)
193 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
194 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
195 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
196 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
198 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
200 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
202 fileutil.make_dirs("cli/test_dump_cap/private")
203 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
204 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
206 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
208 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
210 self.failIf("file renewal secret:" in output, output)
212 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
214 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
215 self.failIf("file renewal secret:" in output, output)
217 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
218 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
220 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
221 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
222 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
225 output = self._dump_cap(u.to_string())
226 self.failUnless("MDMF Read-only URI:" in output, output)
227 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
228 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
229 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
231 u = u.get_verify_cap()
232 output = self._dump_cap(u.to_string())
233 self.failUnless("MDMF Verifier URI:" in output, output)
234 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
235 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
238 def test_dump_cap_chk_directory(self):
239 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
240 uri_extension_hash = hashutil.uri_extension_hash("stuff")
244 u1 = uri.CHKFileURI(key=key,
245 uri_extension_hash=uri_extension_hash,
246 needed_shares=needed_shares,
247 total_shares=total_shares,
249 u = uri.ImmutableDirectoryURI(u1)
251 output = self._dump_cap(u.to_string())
252 self.failUnless("CHK Directory URI:" in output, output)
253 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
254 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
255 self.failUnless("size: 1234" in output, output)
256 self.failUnless("k/N: 25/100" in output, output)
257 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
259 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
261 self.failUnless("file renewal secret: csrvkjgomkyyyil5yo4yk5np37p6oa2ve2hg6xmk2dy7kaxsu6xq" in output, output)
263 u = u.get_verify_cap()
264 output = self._dump_cap(u.to_string())
265 self.failUnless("CHK Directory Verifier URI:" in output, output)
266 self.failIf("key: " in output, output)
267 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
268 self.failUnless("size: 1234" in output, output)
269 self.failUnless("k/N: 25/100" in output, output)
270 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
272 def test_dump_cap_sdmf_directory(self):
273 writekey = "\x01" * 16
274 fingerprint = "\xfe" * 32
275 u1 = uri.WriteableSSKFileURI(writekey, fingerprint)
276 u = uri.DirectoryURI(u1)
278 output = self._dump_cap(u.to_string())
279 self.failUnless("Directory Writeable URI:" in output, output)
280 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
282 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
283 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
285 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
287 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
289 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
291 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
293 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
294 self.failIf("file renewal secret:" in output, output)
296 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
297 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
299 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
300 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
301 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
304 output = self._dump_cap(u.to_string())
305 self.failUnless("Directory Read-only URI:" in output, output)
306 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
307 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
308 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
310 u = u.get_verify_cap()
311 output = self._dump_cap(u.to_string())
312 self.failUnless("Directory Verifier URI:" in output, output)
313 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
314 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
316 def test_dump_cap_mdmf_directory(self):
317 writekey = "\x01" * 16
318 fingerprint = "\xfe" * 32
319 u1 = uri.WriteableMDMFFileURI(writekey, fingerprint)
320 u = uri.MDMFDirectoryURI(u1)
322 output = self._dump_cap(u.to_string())
323 self.failUnless("Directory Writeable URI:" in output, output)
324 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
326 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
327 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
329 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
331 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
333 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
335 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
337 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
338 self.failIf("file renewal secret:" in output, output)
340 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
341 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
343 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
344 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
345 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
348 output = self._dump_cap(u.to_string())
349 self.failUnless("Directory Read-only URI:" in output, output)
350 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
351 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
352 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
354 u = u.get_verify_cap()
355 output = self._dump_cap(u.to_string())
356 self.failUnless("Directory Verifier URI:" in output, output)
357 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
358 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
361 def _catalog_shares(self, *basedirs):
362 o = debug.CatalogSharesOptions()
363 o.stdout,o.stderr = StringIO(), StringIO()
364 args = list(basedirs)
366 debug.catalog_shares(o)
367 out = o.stdout.getvalue()
368 err = o.stderr.getvalue()
371 def test_catalog_shares_error(self):
372 nodedir1 = "cli/test_catalog_shares/node1"
373 sharedir = os.path.join(nodedir1, "storage", "shares", "mq", "mqfblse6m5a6dh45isu2cg7oji")
374 fileutil.make_dirs(sharedir)
375 fileutil.write("cli/test_catalog_shares/node1/storage/shares/mq/not-a-dir", "")
376 # write a bogus share that looks a little bit like CHK
377 fileutil.write(os.path.join(sharedir, "8"),
378 "\x00\x00\x00\x01" + "\xff" * 200) # this triggers an assert
380 nodedir2 = "cli/test_catalog_shares/node2"
381 fileutil.make_dirs(nodedir2)
382 fileutil.write("cli/test_catalog_shares/node1/storage/shares/not-a-dir", "")
384 # now make sure that the 'catalog-shares' commands survives the error
385 out, err = self._catalog_shares(nodedir1, nodedir2)
386 self.failUnlessReallyEqual(out, "", out)
387 self.failUnless("Error processing " in err,
388 "didn't see 'error processing' in '%s'" % err)
389 #self.failUnless(nodedir1 in err,
390 # "didn't see '%s' in '%s'" % (nodedir1, err))
391 # windows mangles the path, and os.path.join isn't enough to make
392 # up for it, so just look for individual strings
393 self.failUnless("node1" in err,
394 "didn't see 'node1' in '%s'" % err)
395 self.failUnless("mqfblse6m5a6dh45isu2cg7oji" in err,
396 "didn't see 'mqfblse6m5a6dh45isu2cg7oji' in '%s'" % err)
398 def test_alias(self):
399 def s128(c): return base32.b2a(c*(128/8))
400 def s256(c): return base32.b2a(c*(256/8))
401 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
402 WA = "URI:DIR2:%s:%s" % (s128("W"), s256("W"))
403 CA = "URI:DIR2:%s:%s" % (s128("C"), s256("C"))
404 aliases = {"tahoe": TA,
408 return get_alias(aliases, path, u"tahoe")
409 uses_lettercolon = common.platform_uses_lettercolon_drivename()
410 self.failUnlessReallyEqual(ga1(u"bare"), (TA, "bare"))
411 self.failUnlessReallyEqual(ga1(u"baredir/file"), (TA, "baredir/file"))
412 self.failUnlessReallyEqual(ga1(u"baredir/file:7"), (TA, "baredir/file:7"))
413 self.failUnlessReallyEqual(ga1(u"tahoe:"), (TA, ""))
414 self.failUnlessReallyEqual(ga1(u"tahoe:file"), (TA, "file"))
415 self.failUnlessReallyEqual(ga1(u"tahoe:dir/file"), (TA, "dir/file"))
416 self.failUnlessReallyEqual(ga1(u"work:"), (WA, ""))
417 self.failUnlessReallyEqual(ga1(u"work:file"), (WA, "file"))
418 self.failUnlessReallyEqual(ga1(u"work:dir/file"), (WA, "dir/file"))
419 # default != None means we really expect a tahoe path, regardless of
420 # whether we're on windows or not. This is what 'tahoe get' uses.
421 self.failUnlessReallyEqual(ga1(u"c:"), (CA, ""))
422 self.failUnlessReallyEqual(ga1(u"c:file"), (CA, "file"))
423 self.failUnlessReallyEqual(ga1(u"c:dir/file"), (CA, "dir/file"))
424 self.failUnlessReallyEqual(ga1(u"URI:stuff"), ("URI:stuff", ""))
425 self.failUnlessReallyEqual(ga1(u"URI:stuff/file"), ("URI:stuff", "file"))
426 self.failUnlessReallyEqual(ga1(u"URI:stuff:./file"), ("URI:stuff", "file"))
427 self.failUnlessReallyEqual(ga1(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
428 self.failUnlessReallyEqual(ga1(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
429 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:")
430 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir")
431 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir/file")
434 return get_alias(aliases, path, None)
435 self.failUnlessReallyEqual(ga2(u"bare"), (DefaultAliasMarker, "bare"))
436 self.failUnlessReallyEqual(ga2(u"baredir/file"),
437 (DefaultAliasMarker, "baredir/file"))
438 self.failUnlessReallyEqual(ga2(u"baredir/file:7"),
439 (DefaultAliasMarker, "baredir/file:7"))
440 self.failUnlessReallyEqual(ga2(u"baredir/sub:1/file:7"),
441 (DefaultAliasMarker, "baredir/sub:1/file:7"))
442 self.failUnlessReallyEqual(ga2(u"tahoe:"), (TA, ""))
443 self.failUnlessReallyEqual(ga2(u"tahoe:file"), (TA, "file"))
444 self.failUnlessReallyEqual(ga2(u"tahoe:dir/file"), (TA, "dir/file"))
445 # on windows, we really want c:foo to indicate a local file.
446 # default==None is what 'tahoe cp' uses.
448 self.failUnlessReallyEqual(ga2(u"c:"), (DefaultAliasMarker, "c:"))
449 self.failUnlessReallyEqual(ga2(u"c:file"), (DefaultAliasMarker, "c:file"))
450 self.failUnlessReallyEqual(ga2(u"c:dir/file"),
451 (DefaultAliasMarker, "c:dir/file"))
453 self.failUnlessReallyEqual(ga2(u"c:"), (CA, ""))
454 self.failUnlessReallyEqual(ga2(u"c:file"), (CA, "file"))
455 self.failUnlessReallyEqual(ga2(u"c:dir/file"), (CA, "dir/file"))
456 self.failUnlessReallyEqual(ga2(u"work:"), (WA, ""))
457 self.failUnlessReallyEqual(ga2(u"work:file"), (WA, "file"))
458 self.failUnlessReallyEqual(ga2(u"work:dir/file"), (WA, "dir/file"))
459 self.failUnlessReallyEqual(ga2(u"URI:stuff"), ("URI:stuff", ""))
460 self.failUnlessReallyEqual(ga2(u"URI:stuff/file"), ("URI:stuff", "file"))
461 self.failUnlessReallyEqual(ga2(u"URI:stuff:./file"), ("URI:stuff", "file"))
462 self.failUnlessReallyEqual(ga2(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
463 self.failUnlessReallyEqual(ga2(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
464 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:")
465 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir")
466 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir/file")
469 old = common.pretend_platform_uses_lettercolon
471 common.pretend_platform_uses_lettercolon = True
472 retval = get_alias(aliases, path, None)
474 common.pretend_platform_uses_lettercolon = old
476 self.failUnlessReallyEqual(ga3(u"bare"), (DefaultAliasMarker, "bare"))
477 self.failUnlessReallyEqual(ga3(u"baredir/file"),
478 (DefaultAliasMarker, "baredir/file"))
479 self.failUnlessReallyEqual(ga3(u"baredir/file:7"),
480 (DefaultAliasMarker, "baredir/file:7"))
481 self.failUnlessReallyEqual(ga3(u"baredir/sub:1/file:7"),
482 (DefaultAliasMarker, "baredir/sub:1/file:7"))
483 self.failUnlessReallyEqual(ga3(u"tahoe:"), (TA, ""))
484 self.failUnlessReallyEqual(ga3(u"tahoe:file"), (TA, "file"))
485 self.failUnlessReallyEqual(ga3(u"tahoe:dir/file"), (TA, "dir/file"))
486 self.failUnlessReallyEqual(ga3(u"c:"), (DefaultAliasMarker, "c:"))
487 self.failUnlessReallyEqual(ga3(u"c:file"), (DefaultAliasMarker, "c:file"))
488 self.failUnlessReallyEqual(ga3(u"c:dir/file"),
489 (DefaultAliasMarker, "c:dir/file"))
490 self.failUnlessReallyEqual(ga3(u"work:"), (WA, ""))
491 self.failUnlessReallyEqual(ga3(u"work:file"), (WA, "file"))
492 self.failUnlessReallyEqual(ga3(u"work:dir/file"), (WA, "dir/file"))
493 self.failUnlessReallyEqual(ga3(u"URI:stuff"), ("URI:stuff", ""))
494 self.failUnlessReallyEqual(ga3(u"URI:stuff:./file"), ("URI:stuff", "file"))
495 self.failUnlessReallyEqual(ga3(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
496 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:")
497 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir")
498 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir/file")
499 # calling get_alias with a path that doesn't include an alias and
500 # default set to something that isn't in the aliases argument should
501 # raise an UnknownAliasError.
503 return get_alias(aliases, path, u"badddefault:")
504 self.failUnlessRaises(common.UnknownAliasError, ga4, u"afile")
505 self.failUnlessRaises(common.UnknownAliasError, ga4, u"a/dir/path/")
508 old = common.pretend_platform_uses_lettercolon
510 common.pretend_platform_uses_lettercolon = True
511 retval = get_alias(aliases, path, u"baddefault:")
513 common.pretend_platform_uses_lettercolon = old
515 self.failUnlessRaises(common.UnknownAliasError, ga5, u"C:\\Windows")
517 def test_alias_tolerance(self):
518 def s128(c): return base32.b2a(c*(128/8))
519 def s256(c): return base32.b2a(c*(256/8))
520 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
521 aliases = {"present": TA,
522 "future": "URI-FROM-FUTURE:ooh:aah"}
524 return get_alias(aliases, path, u"tahoe")
525 self.failUnlessReallyEqual(ga1(u"present:file"), (TA, "file"))
526 # this throws, via assert IDirnodeURI.providedBy(), since get_alias()
527 # wants a dirnode, and the future cap gives us UnknownURI instead.
528 self.failUnlessRaises(AssertionError, ga1, u"future:stuff")
530 def test_listdir_unicode_good(self):
531 filenames = [u'L\u00F4zane', u'Bern', u'Gen\u00E8ve'] # must be NFC
533 for name in filenames:
534 self.skip_if_cannot_represent_filename(name)
536 basedir = "cli/common/listdir_unicode_good"
537 fileutil.make_dirs(basedir)
539 for name in filenames:
540 open(os.path.join(unicode(basedir), name), "wb").close()
542 for file in listdir_unicode(unicode(basedir)):
543 self.failUnlessIn(normalize(file), filenames)
545 def test_exception_catcher(self):
546 self.basedir = "cli/exception_catcher"
549 exc = Exception("canary")
552 ns.runner_called = False
553 def call_runner(args, install_node_control=True):
554 ns.runner_called = True
555 self.failUnlessEqual(install_node_control, True)
558 ns.sys_exit_called = False
559 def call_sys_exit(exitcode):
560 ns.sys_exit_called = True
561 self.failUnlessEqual(exitcode, 1)
563 patcher = MonkeyPatcher((runner, 'runner', call_runner),
564 (sys, 'argv', ["tahoe"]),
565 (sys, 'exit', call_sys_exit),
566 (sys, 'stderr', stderr))
567 patcher.runWithPatches(runner.run)
569 self.failUnless(ns.runner_called)
570 self.failUnless(ns.sys_exit_called)
571 self.failUnlessIn(str(exc), stderr.getvalue())
574 class Help(unittest.TestCase):
575 def failUnlessInNormalized(self, x, y):
576 # helper function to deal with the --help output being wrapped to
577 # various widths, depending on the $COLUMNS environment variable
578 self.failUnlessIn(x.replace("\n", " "), y.replace("\n", " "))
581 help = str(cli.GetOptions())
582 self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help)
583 self.failUnlessIn("% tahoe get FOO |less", help)
586 help = str(cli.PutOptions())
587 self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help)
588 self.failUnlessIn("% cat FILE | tahoe put", help)
591 help = str(cli.ListOptions())
592 self.failUnlessIn("[options] [PATH]", help)
594 def test_unlink(self):
595 help = str(cli.UnlinkOptions())
596 self.failUnlessIn("[options] REMOTE_FILE", help)
599 help = str(cli.RmOptions())
600 self.failUnlessIn("[options] REMOTE_FILE", help)
603 help = str(cli.MvOptions())
604 self.failUnlessIn("[options] FROM TO", help)
605 self.failUnlessInNormalized("Use 'tahoe mv' to move files", help)
608 help = str(cli.CpOptions())
609 self.failUnlessIn("[options] FROM.. TO", help)
610 self.failUnlessInNormalized("Use 'tahoe cp' to copy files", help)
613 help = str(cli.LnOptions())
614 self.failUnlessIn("[options] FROM_LINK TO_LINK", help)
615 self.failUnlessInNormalized("Use 'tahoe ln' to duplicate a link", help)
617 def test_mkdir(self):
618 help = str(cli.MakeDirectoryOptions())
619 self.failUnlessIn("[options] [REMOTE_DIR]", help)
620 self.failUnlessInNormalized("Create a new directory", help)
622 def test_backup(self):
623 help = str(cli.BackupOptions())
624 self.failUnlessIn("[options] FROM ALIAS:TO", help)
626 def test_webopen(self):
627 help = str(cli.WebopenOptions())
628 self.failUnlessIn("[options] [ALIAS:PATH]", help)
630 def test_manifest(self):
631 help = str(cli.ManifestOptions())
632 self.failUnlessIn("[options] [ALIAS:PATH]", help)
634 def test_stats(self):
635 help = str(cli.StatsOptions())
636 self.failUnlessIn("[options] [ALIAS:PATH]", help)
638 def test_check(self):
639 help = str(cli.CheckOptions())
640 self.failUnlessIn("[options] [ALIAS:PATH]", help)
642 def test_deep_check(self):
643 help = str(cli.DeepCheckOptions())
644 self.failUnlessIn("[options] [ALIAS:PATH]", help)
646 def test_create_alias(self):
647 help = str(cli.CreateAliasOptions())
648 self.failUnlessIn("[options] ALIAS[:]", help)
650 def test_add_alias(self):
651 help = str(cli.AddAliasOptions())
652 self.failUnlessIn("[options] ALIAS[:] DIRCAP", help)
654 def test_list_aliases(self):
655 help = str(cli.ListAliasesOptions())
656 self.failUnlessIn("[options]", help)
658 def test_start(self):
659 help = str(startstop_node.StartOptions())
660 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
663 help = str(startstop_node.StopOptions())
664 self.failUnlessIn("[options] [NODEDIR]", help)
666 def test_restart(self):
667 help = str(startstop_node.RestartOptions())
668 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
671 help = str(startstop_node.RunOptions())
672 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
674 def test_create_client(self):
675 help = str(create_node.CreateClientOptions())
676 self.failUnlessIn("[options] [NODEDIR]", help)
678 def test_create_node(self):
679 help = str(create_node.CreateNodeOptions())
680 self.failUnlessIn("[options] [NODEDIR]", help)
682 def test_create_introducer(self):
683 help = str(create_node.CreateIntroducerOptions())
684 self.failUnlessIn("[options] NODEDIR", help)
686 def test_debug_trial(self):
687 help = str(debug.TrialOptions())
688 self.failUnlessIn(" [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help)
689 self.failUnlessInNormalized("The 'tahoe debug trial' command uses the correct imports", help)
691 def test_debug_flogtool(self):
692 options = debug.FlogtoolOptions()
694 self.failUnlessIn(" [global-options] debug flogtool ", help)
695 self.failUnlessInNormalized("The 'tahoe debug flogtool' command uses the correct imports", help)
697 for (option, shortcut, oClass, desc) in options.subCommands:
698 subhelp = str(oClass())
699 self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp)
702 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):
703 def _create_test_file(self):
704 data = "puppies" * 1000
705 path = os.path.join(self.basedir, "datafile")
706 fileutil.write(path, data)
709 def test_ln_without_alias(self):
710 # if invoked without an alias when the 'tahoe' alias doesn't
711 # exist, 'tahoe ln' should output a useful error message and not
713 self.basedir = "cli/Ln/ln_without_alias"
715 d = self.do_cli("ln", "from", "to")
716 def _check((rc, out, err)):
717 self.failUnlessReallyEqual(rc, 1)
718 self.failUnlessIn("error:", err)
719 self.failUnlessReallyEqual(out, "")
720 d.addCallback(_check)
721 # Make sure that validation extends to the "to" parameter
722 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
723 d.addCallback(lambda ign: self._create_test_file())
724 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
726 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "to"))
727 d.addCallback(_check)
730 def test_ln_with_nonexistent_alias(self):
731 # If invoked with aliases that don't exist, 'tahoe ln' should
732 # output a useful error message and not a stack trace.
733 self.basedir = "cli/Ln/ln_with_nonexistent_alias"
735 d = self.do_cli("ln", "havasu:from", "havasu:to")
736 def _check((rc, out, err)):
737 self.failUnlessReallyEqual(rc, 1)
738 self.failUnlessIn("error:", err)
739 d.addCallback(_check)
740 # Make sure that validation occurs on the to parameter if the
741 # from parameter passes.
742 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
743 d.addCallback(lambda ign: self._create_test_file())
744 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
746 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "huron:to"))
747 d.addCallback(_check)
751 class Admin(unittest.TestCase):
752 def do_cli(self, *args, **kwargs):
754 stdin = kwargs.get("stdin", "")
755 stdout, stderr = StringIO(), StringIO()
756 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
757 stdin=StringIO(stdin),
758 stdout=stdout, stderr=stderr)
760 return stdout.getvalue(), stderr.getvalue()
764 def test_generate_keypair(self):
765 d = self.do_cli("admin", "generate-keypair")
766 def _done( (stdout, stderr) ):
767 lines = [line.strip() for line in stdout.splitlines()]
768 privkey_bits = lines[0].split()
769 pubkey_bits = lines[1].split()
770 sk_header = "private:"
771 vk_header = "public:"
772 self.failUnlessEqual(privkey_bits[0], sk_header, lines[0])
773 self.failUnlessEqual(pubkey_bits[0], vk_header, lines[1])
774 self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0])
775 self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1])
776 sk_bytes = base32.a2b(keyutil.remove_prefix(privkey_bits[1], "priv-v0-"))
777 sk = ed25519.SigningKey(sk_bytes)
778 vk_bytes = base32.a2b(keyutil.remove_prefix(pubkey_bits[1], "pub-v0-"))
779 self.failUnlessEqual(sk.get_verifying_key_bytes(), vk_bytes)
783 def test_derive_pubkey(self):
784 priv1,pub1 = keyutil.make_keypair()
785 d = self.do_cli("admin", "derive-pubkey", priv1)
786 def _done( (stdout, stderr) ):
787 lines = stdout.split("\n")
788 privkey_line = lines[0].strip()
789 pubkey_line = lines[1].strip()
790 sk_header = "private: priv-v0-"
791 vk_header = "public: pub-v0-"
792 self.failUnless(privkey_line.startswith(sk_header), privkey_line)
793 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line)
794 pub2 = pubkey_line[len(vk_header):]
795 self.failUnlessEqual("pub-v0-"+pub2, pub1)
800 class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
802 self.basedir = "cli/Errors/get"
804 c0 = self.g.clients[0]
807 d = c0.upload(upload.Data(DATA, convergence=""))
809 self.uri_1share = ur.get_uri()
810 self.delete_shares_numbered(ur.get_uri(), range(1,10))
811 d.addCallback(_stash_bad)
813 # the download is abandoned as soon as it's clear that we won't get
814 # enough shares. The one remaining share might be in either the
815 # COMPLETE or the PENDING state.
816 in_complete_msg = "ran out of shares: complete=sh0 pending= overdue= unused= need 3"
817 in_pending_msg = "ran out of shares: complete= pending=Share(sh0-on-fob7vqgd) overdue= unused= need 3"
819 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share))
820 def _check1((rc, out, err)):
821 self.failIfEqual(rc, 0)
822 self.failUnless("410 Gone" in err, err)
823 self.failUnlessIn("NotEnoughSharesError: ", err)
824 self.failUnless(in_complete_msg in err or in_pending_msg in err,
826 d.addCallback(_check1)
828 targetf = os.path.join(self.basedir, "output")
829 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share, targetf))
830 def _check2((rc, out, err)):
831 self.failIfEqual(rc, 0)
832 self.failUnless("410 Gone" in err, err)
833 self.failUnlessIn("NotEnoughSharesError: ", err)
834 self.failUnless(in_complete_msg in err or in_pending_msg in err,
836 self.failIf(os.path.exists(targetf))
837 d.addCallback(_check2)
841 def test_broken_socket(self):
842 # When the http connection breaks (such as when node.url is overwritten
843 # by a confused user), a user friendly error message should be printed.
844 self.basedir = "cli/Errors/test_broken_socket"
847 # Simulate a connection error
848 def _socket_error(*args, **kwargs):
849 raise socket_error('test error')
850 self.patch(allmydata.scripts.common_http.httplib.HTTPConnection,
851 "endheaders", _socket_error)
853 d = self.do_cli("mkdir")
854 def _check_invalid((rc,stdout,stderr)):
855 self.failIfEqual(rc, 0)
856 self.failUnlessIn("Error trying to connect to http://127.0.0.1", stderr)
857 d.addCallback(_check_invalid)
861 class Get(GridTestMixin, CLITestMixin, unittest.TestCase):
862 def test_get_without_alias(self):
863 # 'tahoe get' should output a useful error message when invoked
864 # without an explicit alias and when the default 'tahoe' alias
865 # hasn't been created yet.
866 self.basedir = "cli/Get/get_without_alias"
868 d = self.do_cli('get', 'file')
869 def _check((rc, out, err)):
870 self.failUnlessReallyEqual(rc, 1)
871 self.failUnlessIn("error:", err)
872 self.failUnlessReallyEqual(out, "")
873 d.addCallback(_check)
876 def test_get_with_nonexistent_alias(self):
877 # 'tahoe get' should output a useful error message when invoked with
878 # an explicit alias that doesn't exist.
879 self.basedir = "cli/Get/get_with_nonexistent_alias"
881 d = self.do_cli("get", "nonexistent:file")
882 def _check((rc, out, err)):
883 self.failUnlessReallyEqual(rc, 1)
884 self.failUnlessIn("error:", err)
885 self.failUnlessIn("nonexistent", err)
886 self.failUnlessReallyEqual(out, "")
887 d.addCallback(_check)
891 class Manifest(GridTestMixin, CLITestMixin, unittest.TestCase):
892 def test_manifest_without_alias(self):
893 # 'tahoe manifest' should output a useful error message when invoked
894 # without an explicit alias when the default 'tahoe' alias is
896 self.basedir = "cli/Manifest/manifest_without_alias"
898 d = self.do_cli("manifest")
899 def _check((rc, out, err)):
900 self.failUnlessReallyEqual(rc, 1)
901 self.failUnlessIn("error:", err)
902 self.failUnlessReallyEqual(out, "")
903 d.addCallback(_check)
906 def test_manifest_with_nonexistent_alias(self):
907 # 'tahoe manifest' should output a useful error message when invoked
908 # with an explicit alias that doesn't exist.
909 self.basedir = "cli/Manifest/manifest_with_nonexistent_alias"
911 d = self.do_cli("manifest", "nonexistent:")
912 def _check((rc, out, err)):
913 self.failUnlessReallyEqual(rc, 1)
914 self.failUnlessIn("error:", err)
915 self.failUnlessIn("nonexistent", err)
916 self.failUnlessReallyEqual(out, "")
917 d.addCallback(_check)
921 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase):
922 def test_mkdir(self):
923 self.basedir = os.path.dirname(self.mktemp())
926 d = self.do_cli("create-alias", "tahoe")
927 d.addCallback(lambda res: self.do_cli("mkdir", "test"))
928 def _check((rc, out, err)):
929 self.failUnlessReallyEqual(rc, 0)
930 self.failUnlessReallyEqual(err, "")
931 self.failUnlessIn("URI:", out)
932 d.addCallback(_check)
936 def test_mkdir_mutable_type(self):
937 self.basedir = os.path.dirname(self.mktemp())
939 d = self.do_cli("create-alias", "tahoe")
940 def _check((rc, out, err), st):
941 self.failUnlessReallyEqual(rc, 0)
942 self.failUnlessReallyEqual(err, "")
943 self.failUnlessIn(st, out)
945 def _mkdir(ign, mutable_type, uri_prefix, dirname):
946 d2 = self.do_cli("mkdir", "--format="+mutable_type, dirname)
947 d2.addCallback(_check, uri_prefix)
948 def _stash_filecap(cap):
949 u = uri.from_string(cap)
950 fn_uri = u.get_filenode_cap()
951 self._filecap = fn_uri.to_string()
952 d2.addCallback(_stash_filecap)
953 d2.addCallback(lambda ign: self.do_cli("ls", "--json", dirname))
954 d2.addCallback(_check, uri_prefix)
955 d2.addCallback(lambda ign: self.do_cli("ls", "--json", self._filecap))
956 d2.addCallback(_check, '"format": "%s"' % (mutable_type.upper(),))
959 d.addCallback(_mkdir, "sdmf", "URI:DIR2", "tahoe:foo")
960 d.addCallback(_mkdir, "SDMF", "URI:DIR2", "tahoe:foo2")
961 d.addCallback(_mkdir, "mdmf", "URI:DIR2-MDMF", "tahoe:bar")
962 d.addCallback(_mkdir, "MDMF", "URI:DIR2-MDMF", "tahoe:bar2")
965 def test_mkdir_mutable_type_unlinked(self):
966 self.basedir = os.path.dirname(self.mktemp())
968 d = self.do_cli("mkdir", "--format=SDMF")
969 def _check((rc, out, err), st):
970 self.failUnlessReallyEqual(rc, 0)
971 self.failUnlessReallyEqual(err, "")
972 self.failUnlessIn(st, out)
974 d.addCallback(_check, "URI:DIR2")
975 def _stash_dircap(cap):
977 # Now we're going to feed the cap into uri.from_string...
978 u = uri.from_string(cap)
979 # ...grab the underlying filenode uri.
980 fn_uri = u.get_filenode_cap()
982 self._filecap = fn_uri.to_string()
983 d.addCallback(_stash_dircap)
984 d.addCallback(lambda res: self.do_cli("ls", "--json",
986 d.addCallback(_check, '"format": "SDMF"')
987 d.addCallback(lambda res: self.do_cli("mkdir", "--format=MDMF"))
988 d.addCallback(_check, "URI:DIR2-MDMF")
989 d.addCallback(_stash_dircap)
990 d.addCallback(lambda res: self.do_cli("ls", "--json",
992 d.addCallback(_check, '"format": "MDMF"')
995 def test_mkdir_bad_mutable_type(self):
996 o = cli.MakeDirectoryOptions()
997 self.failUnlessRaises(usage.UsageError,
1001 def test_mkdir_unicode(self):
1002 self.basedir = os.path.dirname(self.mktemp())
1006 motorhead_arg = u"tahoe:Mot\u00F6rhead".encode(get_io_encoding())
1007 except UnicodeEncodeError:
1008 raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
1010 d = self.do_cli("create-alias", "tahoe")
1011 d.addCallback(lambda res: self.do_cli("mkdir", motorhead_arg))
1012 def _check((rc, out, err)):
1013 self.failUnlessReallyEqual(rc, 0)
1014 self.failUnlessReallyEqual(err, "")
1015 self.failUnlessIn("URI:", out)
1016 d.addCallback(_check)
1020 def test_mkdir_with_nonexistent_alias(self):
1021 # when invoked with an alias that doesn't exist, 'tahoe mkdir' should
1022 # output a sensible error message rather than a stack trace.
1023 self.basedir = "cli/Mkdir/mkdir_with_nonexistent_alias"
1025 d = self.do_cli("mkdir", "havasu:")
1026 def _check((rc, out, err)):
1027 self.failUnlessReallyEqual(rc, 1)
1028 self.failUnlessIn("error:", err)
1029 self.failUnlessReallyEqual(out, "")
1030 d.addCallback(_check)
1034 class Unlink(GridTestMixin, CLITestMixin, unittest.TestCase):
1037 def _create_test_file(self):
1038 data = "puppies" * 1000
1039 path = os.path.join(self.basedir, "datafile")
1040 fileutil.write(path, data)
1041 self.datafile = path
1043 def test_unlink_without_alias(self):
1044 # 'tahoe unlink' should behave sensibly when invoked without an explicit
1045 # alias before the default 'tahoe' alias has been created.
1046 self.basedir = "cli/Unlink/%s_without_alias" % (self.command,)
1048 d = self.do_cli(self.command, "afile")
1049 def _check((rc, out, err)):
1050 self.failUnlessReallyEqual(rc, 1)
1051 self.failUnlessIn("error:", err)
1052 self.failUnlessReallyEqual(out, "")
1053 d.addCallback(_check)
1055 d.addCallback(lambda ign: self.do_cli(self.command, "afile"))
1056 d.addCallback(_check)
1059 def test_unlink_with_nonexistent_alias(self):
1060 # 'tahoe unlink' should behave sensibly when invoked with an explicit
1061 # alias that doesn't exist.
1062 self.basedir = "cli/Unlink/%s_with_nonexistent_alias" % (self.command,)
1064 d = self.do_cli(self.command, "nonexistent:afile")
1065 def _check((rc, out, err)):
1066 self.failUnlessReallyEqual(rc, 1)
1067 self.failUnlessIn("error:", err)
1068 self.failUnlessIn("nonexistent", err)
1069 self.failUnlessReallyEqual(out, "")
1070 d.addCallback(_check)
1072 d.addCallback(lambda ign: self.do_cli(self.command, "nonexistent:afile"))
1073 d.addCallback(_check)
1076 def test_unlink_without_path(self):
1077 # 'tahoe unlink' should give a sensible error message when invoked without a path.
1078 self.basedir = "cli/Unlink/%s_without_path" % (self.command,)
1080 self._create_test_file()
1081 d = self.do_cli("create-alias", "tahoe")
1082 d.addCallback(lambda ign: self.do_cli("put", self.datafile, "tahoe:test"))
1083 def _do_unlink((rc, out, err)):
1084 self.failUnlessReallyEqual(rc, 0)
1085 self.failUnless(out.startswith("URI:"), out)
1086 return self.do_cli(self.command, out.strip('\n'))
1087 d.addCallback(_do_unlink)
1089 def _check((rc, out, err)):
1090 self.failUnlessReallyEqual(rc, 1)
1091 self.failUnlessIn("'tahoe %s'" % (self.command,), err)
1092 self.failUnlessIn("path must be given", err)
1093 self.failUnlessReallyEqual(out, "")
1094 d.addCallback(_check)
1099 """Test that 'tahoe rm' behaves in the same way as 'tahoe unlink'."""
1103 class Stats(GridTestMixin, CLITestMixin, unittest.TestCase):
1104 def test_empty_directory(self):
1105 self.basedir = "cli/Stats/empty_directory"
1107 c0 = self.g.clients[0]
1109 d = c0.create_dirnode()
1112 self.rooturi = n.get_uri()
1113 d.addCallback(_stash_root)
1115 # make sure we can get stats on an empty directory too
1116 d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
1117 def _check_stats((rc, out, err)):
1118 self.failUnlessReallyEqual(err, "")
1119 self.failUnlessReallyEqual(rc, 0)
1120 lines = out.splitlines()
1121 self.failUnlessIn(" count-immutable-files: 0", lines)
1122 self.failUnlessIn(" count-mutable-files: 0", lines)
1123 self.failUnlessIn(" count-literal-files: 0", lines)
1124 self.failUnlessIn(" count-directories: 1", lines)
1125 self.failUnlessIn(" size-immutable-files: 0", lines)
1126 self.failIfIn("Size Histogram:", lines)
1127 d.addCallback(_check_stats)
1131 def test_stats_without_alias(self):
1132 # when invoked with no explicit alias and before the default 'tahoe'
1133 # alias is created, 'tahoe stats' should output an informative error
1134 # message, not a stack trace.
1135 self.basedir = "cli/Stats/stats_without_alias"
1137 d = self.do_cli("stats")
1138 def _check((rc, out, err)):
1139 self.failUnlessReallyEqual(rc, 1)
1140 self.failUnlessIn("error:", err)
1141 self.failUnlessReallyEqual(out, "")
1142 d.addCallback(_check)
1145 def test_stats_with_nonexistent_alias(self):
1146 # when invoked with an explicit alias that doesn't exist,
1147 # 'tahoe stats' should output a useful error message.
1148 self.basedir = "cli/Stats/stats_with_nonexistent_alias"
1150 d = self.do_cli("stats", "havasu:")
1151 def _check((rc, out, err)):
1152 self.failUnlessReallyEqual(rc, 1)
1153 self.failUnlessIn("error:", err)
1154 self.failUnlessReallyEqual(out, "")
1155 d.addCallback(_check)
1159 class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase):
1160 def test_webopen_with_nonexistent_alias(self):
1161 # when invoked with an alias that doesn't exist, 'tahoe webopen'
1162 # should output an informative error message instead of a stack
1164 self.basedir = "cli/Webopen/webopen_with_nonexistent_alias"
1166 d = self.do_cli("webopen", "fake:")
1167 def _check((rc, out, err)):
1168 self.failUnlessReallyEqual(rc, 1)
1169 self.failUnlessIn("error:", err)
1170 self.failUnlessReallyEqual(out, "")
1171 d.addCallback(_check)
1174 def test_webopen(self):
1175 # TODO: replace with @patch that supports Deferreds.
1177 def call_webbrowser_open(url):
1178 self.failUnlessIn(self.alias_uri.replace(':', '%3A'), url)
1179 self.webbrowser_open_called = True
1181 webbrowser.open = self.old_webbrowser_open
1184 self.old_webbrowser_open = webbrowser.open
1186 webbrowser.open = call_webbrowser_open
1188 self.basedir = "cli/Webopen/webopen"
1190 d = self.do_cli("create-alias", "alias:")
1191 def _check_alias((rc, out, err)):
1192 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1193 self.failUnlessIn("Alias 'alias' created", out)
1194 self.failUnlessReallyEqual(err, "")
1195 self.alias_uri = get_aliases(self.get_clientdir())["alias"]
1196 d.addCallback(_check_alias)
1197 d.addCallback(lambda res: self.do_cli("webopen", "alias:"))
1198 def _check_webopen((rc, out, err)):
1199 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1200 self.failUnlessReallyEqual(out, "")
1201 self.failUnlessReallyEqual(err, "")
1202 self.failUnless(self.webbrowser_open_called)
1203 d.addCallback(_check_webopen)
1210 class Options(ReallyEqualMixin, unittest.TestCase):
1211 # this test case only looks at argument-processing and simple stuff.
1213 def parse(self, args, stdout=None):
1214 o = runner.Options()
1215 if stdout is not None:
1217 o.parseOptions(args)
1218 while hasattr(o, "subOptions"):
1222 def test_list(self):
1223 fileutil.rm_dir("cli/test_options")
1224 fileutil.make_dirs("cli/test_options")
1225 fileutil.make_dirs("cli/test_options/private")
1226 fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n")
1227 filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16,
1228 fingerprint="\x00"*32)
1229 private_uri = uri.DirectoryURI(filenode_uri).to_string()
1230 fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n")
1231 def parse2(args): return parse_options("cli/test_options", "ls", args)
1233 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1234 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1235 self.failUnlessEqual(o.where, u"")
1237 o = parse2(["--node-url", "http://example.org:8111/"])
1238 self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
1239 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1240 self.failUnlessEqual(o.where, u"")
1242 o = parse2(["--dir-cap", "root"])
1243 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1244 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
1245 self.failUnlessEqual(o.where, u"")
1247 other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
1248 fingerprint="\x11"*32)
1249 other_uri = uri.DirectoryURI(other_filenode_uri).to_string()
1250 o = parse2(["--dir-cap", other_uri])
1251 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1252 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1253 self.failUnlessEqual(o.where, u"")
1255 o = parse2(["--dir-cap", other_uri, "subdir"])
1256 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1257 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1258 self.failUnlessEqual(o.where, u"subdir")
1260 self.failUnlessRaises(usage.UsageError, parse2,
1261 ["--node-url", "NOT-A-URL"])
1263 o = parse2(["--node-url", "http://localhost:8080"])
1264 self.failUnlessEqual(o["node-url"], "http://localhost:8080/")
1266 o = parse2(["--node-url", "https://localhost/"])
1267 self.failUnlessEqual(o["node-url"], "https://localhost/")
1269 def test_version(self):
1270 # "tahoe --version" dumps text to stdout and exits
1272 self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout)
1273 self.failUnlessIn(allmydata.__appname__ + ":", stdout.getvalue())
1274 # but "tahoe SUBCOMMAND --version" should be rejected
1275 self.failUnlessRaises(usage.UsageError, self.parse,
1276 ["start", "--version"])
1277 self.failUnlessRaises(usage.UsageError, self.parse,
1278 ["start", "--version-and-path"])
1280 def test_quiet(self):
1281 # accepted as an overall option, but not on subcommands
1282 o = self.parse(["--quiet", "start"])
1283 self.failUnless(o.parent["quiet"])
1284 self.failUnlessRaises(usage.UsageError, self.parse,
1285 ["start", "--quiet"])
1287 def test_basedir(self):
1288 # accept a --node-directory option before the verb, or a --basedir
1289 # option after, or a basedir argument after, but none in the wrong
1290 # place, and not more than one of the three.
1291 o = self.parse(["start"])
1292 self.failUnlessReallyEqual(o["basedir"], os.path.join(fileutil.abspath_expanduser_unicode(u"~"),
1294 o = self.parse(["start", "here"])
1295 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1296 o = self.parse(["start", "--basedir", "there"])
1297 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1298 o = self.parse(["--node-directory", "there", "start"])
1299 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1301 o = self.parse(["start", "here", "--nodaemon"])
1302 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1304 self.failUnlessRaises(usage.UsageError, self.parse,
1305 ["--basedir", "there", "start"])
1306 self.failUnlessRaises(usage.UsageError, self.parse,
1307 ["start", "--node-directory", "there"])
1309 self.failUnlessRaises(usage.UsageError, self.parse,
1310 ["--node-directory=there",
1311 "start", "--basedir=here"])
1312 self.failUnlessRaises(usage.UsageError, self.parse,
1313 ["start", "--basedir=here", "anywhere"])
1314 self.failUnlessRaises(usage.UsageError, self.parse,
1315 ["--node-directory=there",
1316 "start", "anywhere"])
1317 self.failUnlessRaises(usage.UsageError, self.parse,
1318 ["--node-directory=there",
1319 "start", "--basedir=here", "anywhere"])
1321 self.failUnlessRaises(usage.UsageError, self.parse,
1322 ["--node-directory=there", "start", "--nodaemon"])
1323 self.failUnlessRaises(usage.UsageError, self.parse,
1324 ["start", "--basedir=here", "--nodaemon"])