3 from twisted.trial import unittest
4 from cStringIO import StringIO
7 from mock import Mock, call
10 from allmydata.util import fileutil, hashutil, base32, keyutil
11 from allmydata import uri
12 from allmydata.immutable import upload
13 from allmydata.dirnode import normalize
14 from allmydata.scripts.common_http import socket_error
15 import allmydata.scripts.common_http
16 from pycryptopp.publickey import ed25519
18 # Test that the scripts can be imported.
19 from allmydata.scripts import create_node, debug, keygen, startstop_node, \
20 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls, \
21 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen
22 _hush_pyflakes = [create_node, debug, keygen, startstop_node,
23 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls,
24 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen]
26 from allmydata.scripts import common
27 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases, get_alias, \
30 from allmydata.scripts import cli, debug, runner
31 from allmydata.test.common_util import ReallyEqualMixin
32 from allmydata.test.no_network import GridTestMixin
33 from twisted.internet import threads # CLI tests use deferToThread
34 from twisted.python import usage
36 from allmydata.util.assertutil import precondition
37 from allmydata.util.encodingutil import listdir_unicode, unicode_platform, \
38 get_io_encoding, get_filesystem_encoding
40 timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s
42 def parse_options(basedir, command, args):
44 o.parseOptions(["--node-directory", basedir, command] + args)
45 while hasattr(o, "subOptions"):
49 class CLITestMixin(ReallyEqualMixin):
50 def do_cli(self, verb, *args, **kwargs):
52 "--node-directory", self.get_clientdir(),
54 argv = nodeargs + [verb] + list(args)
55 stdin = kwargs.get("stdin", "")
56 stdout, stderr = StringIO(), StringIO()
57 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
58 stdin=StringIO(stdin),
59 stdout=stdout, stderr=stderr)
61 return rc, stdout.getvalue(), stderr.getvalue()
65 def skip_if_cannot_represent_filename(self, u):
66 precondition(isinstance(u, unicode))
68 enc = get_filesystem_encoding()
69 if not unicode_platform():
72 except UnicodeEncodeError:
73 raise unittest.SkipTest("A non-ASCII filename could not be encoded on this platform.")
76 class CLI(CLITestMixin, unittest.TestCase):
77 def _dump_cap(self, *args):
78 config = debug.DumpCapOptions()
79 config.stdout,config.stderr = StringIO(), StringIO()
80 config.parseOptions(args)
81 debug.dump_cap(config)
82 self.failIf(config.stderr.getvalue())
83 output = config.stdout.getvalue()
86 def test_dump_cap_chk(self):
87 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
88 uri_extension_hash = hashutil.uri_extension_hash("stuff")
92 u = uri.CHKFileURI(key=key,
93 uri_extension_hash=uri_extension_hash,
94 needed_shares=needed_shares,
95 total_shares=total_shares,
97 output = self._dump_cap(u.to_string())
98 self.failUnless("CHK File:" in output, output)
99 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
100 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
101 self.failUnless("size: 1234" in output, output)
102 self.failUnless("k/N: 25/100" in output, output)
103 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
105 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
107 self.failUnless("client renewal secret: znxmki5zdibb5qlt46xbdvk2t55j7hibejq3i5ijyurkr6m6jkhq" in output, output)
109 output = self._dump_cap(u.get_verify_cap().to_string())
110 self.failIf("key: " in output, output)
111 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
112 self.failUnless("size: 1234" in output, output)
113 self.failUnless("k/N: 25/100" in output, output)
114 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
116 prefixed_u = "http://127.0.0.1/uri/%s" % urllib.quote(u.to_string())
117 output = self._dump_cap(prefixed_u)
118 self.failUnless("CHK File:" in output, output)
119 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
120 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
121 self.failUnless("size: 1234" in output, output)
122 self.failUnless("k/N: 25/100" in output, output)
123 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
125 def test_dump_cap_lit(self):
126 u = uri.LiteralFileURI("this is some data")
127 output = self._dump_cap(u.to_string())
128 self.failUnless("Literal File URI:" in output, output)
129 self.failUnless("data: 'this is some data'" in output, output)
131 def test_dump_cap_sdmf(self):
132 writekey = "\x01" * 16
133 fingerprint = "\xfe" * 32
134 u = uri.WriteableSSKFileURI(writekey, fingerprint)
136 output = self._dump_cap(u.to_string())
137 self.failUnless("SDMF Writeable URI:" in output, output)
138 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
139 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
140 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
141 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
143 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
145 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
147 fileutil.make_dirs("cli/test_dump_cap/private")
148 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
149 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
151 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
153 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
155 self.failIf("file renewal secret:" in output, output)
157 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
159 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
160 self.failIf("file renewal secret:" in output, output)
162 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
163 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
165 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
166 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
167 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
170 output = self._dump_cap(u.to_string())
171 self.failUnless("SDMF Read-only URI:" in output, output)
172 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
173 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
174 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
176 u = u.get_verify_cap()
177 output = self._dump_cap(u.to_string())
178 self.failUnless("SDMF Verifier URI:" in output, output)
179 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
180 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
182 def test_dump_cap_mdmf(self):
183 writekey = "\x01" * 16
184 fingerprint = "\xfe" * 32
185 u = uri.WriteableMDMFFileURI(writekey, fingerprint)
187 output = self._dump_cap(u.to_string())
188 self.failUnless("MDMF Writeable URI:" in output, output)
189 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
190 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
191 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
192 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
194 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
196 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
198 fileutil.make_dirs("cli/test_dump_cap/private")
199 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
200 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
202 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
204 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
206 self.failIf("file renewal secret:" in output, output)
208 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
210 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
211 self.failIf("file renewal secret:" in output, output)
213 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
214 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
216 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
217 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
218 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
221 output = self._dump_cap(u.to_string())
222 self.failUnless("MDMF Read-only URI:" in output, output)
223 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
224 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
225 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
227 u = u.get_verify_cap()
228 output = self._dump_cap(u.to_string())
229 self.failUnless("MDMF Verifier URI:" in output, output)
230 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
231 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
234 def test_dump_cap_chk_directory(self):
235 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
236 uri_extension_hash = hashutil.uri_extension_hash("stuff")
240 u1 = uri.CHKFileURI(key=key,
241 uri_extension_hash=uri_extension_hash,
242 needed_shares=needed_shares,
243 total_shares=total_shares,
245 u = uri.ImmutableDirectoryURI(u1)
247 output = self._dump_cap(u.to_string())
248 self.failUnless("CHK Directory URI:" in output, output)
249 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
250 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
251 self.failUnless("size: 1234" in output, output)
252 self.failUnless("k/N: 25/100" in output, output)
253 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
255 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
257 self.failUnless("file renewal secret: csrvkjgomkyyyil5yo4yk5np37p6oa2ve2hg6xmk2dy7kaxsu6xq" in output, output)
259 u = u.get_verify_cap()
260 output = self._dump_cap(u.to_string())
261 self.failUnless("CHK Directory Verifier URI:" in output, output)
262 self.failIf("key: " in output, output)
263 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
264 self.failUnless("size: 1234" in output, output)
265 self.failUnless("k/N: 25/100" in output, output)
266 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
268 def test_dump_cap_sdmf_directory(self):
269 writekey = "\x01" * 16
270 fingerprint = "\xfe" * 32
271 u1 = uri.WriteableSSKFileURI(writekey, fingerprint)
272 u = uri.DirectoryURI(u1)
274 output = self._dump_cap(u.to_string())
275 self.failUnless("Directory Writeable URI:" in output, output)
276 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
278 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
279 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
281 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
283 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
285 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
287 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
289 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
290 self.failIf("file renewal secret:" in output, output)
292 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
293 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
295 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
296 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
297 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
300 output = self._dump_cap(u.to_string())
301 self.failUnless("Directory Read-only URI:" in output, output)
302 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
303 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
304 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
306 u = u.get_verify_cap()
307 output = self._dump_cap(u.to_string())
308 self.failUnless("Directory Verifier URI:" in output, output)
309 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
310 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
312 def test_dump_cap_mdmf_directory(self):
313 writekey = "\x01" * 16
314 fingerprint = "\xfe" * 32
315 u1 = uri.WriteableMDMFFileURI(writekey, fingerprint)
316 u = uri.MDMFDirectoryURI(u1)
318 output = self._dump_cap(u.to_string())
319 self.failUnless("Directory Writeable URI:" in output, output)
320 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
322 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
323 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
325 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
327 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
329 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
331 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
333 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
334 self.failIf("file renewal secret:" in output, output)
336 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
337 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
339 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
340 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
341 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
344 output = self._dump_cap(u.to_string())
345 self.failUnless("Directory Read-only URI:" in output, output)
346 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
347 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
348 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
350 u = u.get_verify_cap()
351 output = self._dump_cap(u.to_string())
352 self.failUnless("Directory Verifier URI:" in output, output)
353 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
354 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
357 def _catalog_shares(self, *basedirs):
358 o = debug.CatalogSharesOptions()
359 o.stdout,o.stderr = StringIO(), StringIO()
360 args = list(basedirs)
362 debug.catalog_shares(o)
363 out = o.stdout.getvalue()
364 err = o.stderr.getvalue()
367 def test_catalog_shares_error(self):
368 nodedir1 = "cli/test_catalog_shares/node1"
369 sharedir = os.path.join(nodedir1, "storage", "shares", "mq", "mqfblse6m5a6dh45isu2cg7oji")
370 fileutil.make_dirs(sharedir)
371 fileutil.write("cli/test_catalog_shares/node1/storage/shares/mq/not-a-dir", "")
372 # write a bogus share that looks a little bit like CHK
373 fileutil.write(os.path.join(sharedir, "8"),
374 "\x00\x00\x00\x01" + "\xff" * 200) # this triggers an assert
376 nodedir2 = "cli/test_catalog_shares/node2"
377 fileutil.make_dirs(nodedir2)
378 fileutil.write("cli/test_catalog_shares/node1/storage/shares/not-a-dir", "")
380 # now make sure that the 'catalog-shares' commands survives the error
381 out, err = self._catalog_shares(nodedir1, nodedir2)
382 self.failUnlessReallyEqual(out, "", out)
383 self.failUnless("Error processing " in err,
384 "didn't see 'error processing' in '%s'" % err)
385 #self.failUnless(nodedir1 in err,
386 # "didn't see '%s' in '%s'" % (nodedir1, err))
387 # windows mangles the path, and os.path.join isn't enough to make
388 # up for it, so just look for individual strings
389 self.failUnless("node1" in err,
390 "didn't see 'node1' in '%s'" % err)
391 self.failUnless("mqfblse6m5a6dh45isu2cg7oji" in err,
392 "didn't see 'mqfblse6m5a6dh45isu2cg7oji' in '%s'" % err)
394 def test_alias(self):
395 def s128(c): return base32.b2a(c*(128/8))
396 def s256(c): return base32.b2a(c*(256/8))
397 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
398 WA = "URI:DIR2:%s:%s" % (s128("W"), s256("W"))
399 CA = "URI:DIR2:%s:%s" % (s128("C"), s256("C"))
400 aliases = {"tahoe": TA,
404 return get_alias(aliases, path, u"tahoe")
405 uses_lettercolon = common.platform_uses_lettercolon_drivename()
406 self.failUnlessReallyEqual(ga1(u"bare"), (TA, "bare"))
407 self.failUnlessReallyEqual(ga1(u"baredir/file"), (TA, "baredir/file"))
408 self.failUnlessReallyEqual(ga1(u"baredir/file:7"), (TA, "baredir/file:7"))
409 self.failUnlessReallyEqual(ga1(u"tahoe:"), (TA, ""))
410 self.failUnlessReallyEqual(ga1(u"tahoe:file"), (TA, "file"))
411 self.failUnlessReallyEqual(ga1(u"tahoe:dir/file"), (TA, "dir/file"))
412 self.failUnlessReallyEqual(ga1(u"work:"), (WA, ""))
413 self.failUnlessReallyEqual(ga1(u"work:file"), (WA, "file"))
414 self.failUnlessReallyEqual(ga1(u"work:dir/file"), (WA, "dir/file"))
415 # default != None means we really expect a tahoe path, regardless of
416 # whether we're on windows or not. This is what 'tahoe get' uses.
417 self.failUnlessReallyEqual(ga1(u"c:"), (CA, ""))
418 self.failUnlessReallyEqual(ga1(u"c:file"), (CA, "file"))
419 self.failUnlessReallyEqual(ga1(u"c:dir/file"), (CA, "dir/file"))
420 self.failUnlessReallyEqual(ga1(u"URI:stuff"), ("URI:stuff", ""))
421 self.failUnlessReallyEqual(ga1(u"URI:stuff/file"), ("URI:stuff", "file"))
422 self.failUnlessReallyEqual(ga1(u"URI:stuff:./file"), ("URI:stuff", "file"))
423 self.failUnlessReallyEqual(ga1(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
424 self.failUnlessReallyEqual(ga1(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
425 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:")
426 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir")
427 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir/file")
430 return get_alias(aliases, path, None)
431 self.failUnlessReallyEqual(ga2(u"bare"), (DefaultAliasMarker, "bare"))
432 self.failUnlessReallyEqual(ga2(u"baredir/file"),
433 (DefaultAliasMarker, "baredir/file"))
434 self.failUnlessReallyEqual(ga2(u"baredir/file:7"),
435 (DefaultAliasMarker, "baredir/file:7"))
436 self.failUnlessReallyEqual(ga2(u"baredir/sub:1/file:7"),
437 (DefaultAliasMarker, "baredir/sub:1/file:7"))
438 self.failUnlessReallyEqual(ga2(u"tahoe:"), (TA, ""))
439 self.failUnlessReallyEqual(ga2(u"tahoe:file"), (TA, "file"))
440 self.failUnlessReallyEqual(ga2(u"tahoe:dir/file"), (TA, "dir/file"))
441 # on windows, we really want c:foo to indicate a local file.
442 # default==None is what 'tahoe cp' uses.
444 self.failUnlessReallyEqual(ga2(u"c:"), (DefaultAliasMarker, "c:"))
445 self.failUnlessReallyEqual(ga2(u"c:file"), (DefaultAliasMarker, "c:file"))
446 self.failUnlessReallyEqual(ga2(u"c:dir/file"),
447 (DefaultAliasMarker, "c:dir/file"))
449 self.failUnlessReallyEqual(ga2(u"c:"), (CA, ""))
450 self.failUnlessReallyEqual(ga2(u"c:file"), (CA, "file"))
451 self.failUnlessReallyEqual(ga2(u"c:dir/file"), (CA, "dir/file"))
452 self.failUnlessReallyEqual(ga2(u"work:"), (WA, ""))
453 self.failUnlessReallyEqual(ga2(u"work:file"), (WA, "file"))
454 self.failUnlessReallyEqual(ga2(u"work:dir/file"), (WA, "dir/file"))
455 self.failUnlessReallyEqual(ga2(u"URI:stuff"), ("URI:stuff", ""))
456 self.failUnlessReallyEqual(ga2(u"URI:stuff/file"), ("URI:stuff", "file"))
457 self.failUnlessReallyEqual(ga2(u"URI:stuff:./file"), ("URI:stuff", "file"))
458 self.failUnlessReallyEqual(ga2(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
459 self.failUnlessReallyEqual(ga2(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
460 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:")
461 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir")
462 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir/file")
465 old = common.pretend_platform_uses_lettercolon
467 common.pretend_platform_uses_lettercolon = True
468 retval = get_alias(aliases, path, None)
470 common.pretend_platform_uses_lettercolon = old
472 self.failUnlessReallyEqual(ga3(u"bare"), (DefaultAliasMarker, "bare"))
473 self.failUnlessReallyEqual(ga3(u"baredir/file"),
474 (DefaultAliasMarker, "baredir/file"))
475 self.failUnlessReallyEqual(ga3(u"baredir/file:7"),
476 (DefaultAliasMarker, "baredir/file:7"))
477 self.failUnlessReallyEqual(ga3(u"baredir/sub:1/file:7"),
478 (DefaultAliasMarker, "baredir/sub:1/file:7"))
479 self.failUnlessReallyEqual(ga3(u"tahoe:"), (TA, ""))
480 self.failUnlessReallyEqual(ga3(u"tahoe:file"), (TA, "file"))
481 self.failUnlessReallyEqual(ga3(u"tahoe:dir/file"), (TA, "dir/file"))
482 self.failUnlessReallyEqual(ga3(u"c:"), (DefaultAliasMarker, "c:"))
483 self.failUnlessReallyEqual(ga3(u"c:file"), (DefaultAliasMarker, "c:file"))
484 self.failUnlessReallyEqual(ga3(u"c:dir/file"),
485 (DefaultAliasMarker, "c:dir/file"))
486 self.failUnlessReallyEqual(ga3(u"work:"), (WA, ""))
487 self.failUnlessReallyEqual(ga3(u"work:file"), (WA, "file"))
488 self.failUnlessReallyEqual(ga3(u"work:dir/file"), (WA, "dir/file"))
489 self.failUnlessReallyEqual(ga3(u"URI:stuff"), ("URI:stuff", ""))
490 self.failUnlessReallyEqual(ga3(u"URI:stuff:./file"), ("URI:stuff", "file"))
491 self.failUnlessReallyEqual(ga3(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
492 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:")
493 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir")
494 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir/file")
495 # calling get_alias with a path that doesn't include an alias and
496 # default set to something that isn't in the aliases argument should
497 # raise an UnknownAliasError.
499 return get_alias(aliases, path, u"badddefault:")
500 self.failUnlessRaises(common.UnknownAliasError, ga4, u"afile")
501 self.failUnlessRaises(common.UnknownAliasError, ga4, u"a/dir/path/")
504 old = common.pretend_platform_uses_lettercolon
506 common.pretend_platform_uses_lettercolon = True
507 retval = get_alias(aliases, path, u"baddefault:")
509 common.pretend_platform_uses_lettercolon = old
511 self.failUnlessRaises(common.UnknownAliasError, ga5, u"C:\\Windows")
513 def test_alias_tolerance(self):
514 def s128(c): return base32.b2a(c*(128/8))
515 def s256(c): return base32.b2a(c*(256/8))
516 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
517 aliases = {"present": TA,
518 "future": "URI-FROM-FUTURE:ooh:aah"}
520 return get_alias(aliases, path, u"tahoe")
521 self.failUnlessReallyEqual(ga1(u"present:file"), (TA, "file"))
522 # this throws, via assert IDirnodeURI.providedBy(), since get_alias()
523 # wants a dirnode, and the future cap gives us UnknownURI instead.
524 self.failUnlessRaises(AssertionError, ga1, u"future:stuff")
526 def test_listdir_unicode_good(self):
527 filenames = [u'L\u00F4zane', u'Bern', u'Gen\u00E8ve'] # must be NFC
529 for name in filenames:
530 self.skip_if_cannot_represent_filename(name)
532 basedir = "cli/common/listdir_unicode_good"
533 fileutil.make_dirs(basedir)
535 for name in filenames:
536 open(os.path.join(unicode(basedir), name), "wb").close()
538 for file in listdir_unicode(unicode(basedir)):
539 self.failUnlessIn(normalize(file), filenames)
541 def test_exception_catcher(self):
542 self.basedir = "cli/exception_catcher"
545 sys_exit_mock = Mock()
547 self.patch(sys, "argv", ["tahoe"])
548 self.patch(runner, "runner", runner_mock)
549 self.patch(sys, "exit", sys_exit_mock)
550 self.patch(sys, "stderr", stderr)
551 exc = Exception("canary")
553 def call_runner(args, install_node_control=True):
555 runner_mock.side_effect = call_runner
558 self.failUnlessEqual(runner_mock.call_args_list, [call([], install_node_control=True)])
559 self.failUnlessEqual(sys_exit_mock.call_args_list, [call(1)])
560 self.failUnlessIn(str(exc), stderr.getvalue())
563 class Help(unittest.TestCase):
565 help = str(cli.GetOptions())
566 self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help)
567 self.failUnlessIn("% tahoe get FOO |less", help)
570 help = str(cli.PutOptions())
571 self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help)
572 self.failUnlessIn("% cat FILE | tahoe put", help)
575 help = str(cli.ListOptions())
576 self.failUnlessIn("[options] [PATH]", help)
578 def test_unlink(self):
579 help = str(cli.UnlinkOptions())
580 self.failUnlessIn("[options] REMOTE_FILE", help)
583 help = str(cli.RmOptions())
584 self.failUnlessIn("[options] REMOTE_FILE", help)
587 help = str(cli.MvOptions())
588 self.failUnlessIn("[options] FROM TO", help)
589 self.failUnlessIn("Use 'tahoe mv' to move files", help)
592 help = str(cli.CpOptions())
593 self.failUnlessIn("[options] FROM.. TO", help)
594 self.failUnlessIn("Use 'tahoe cp' to copy files", help)
597 help = str(cli.LnOptions())
598 self.failUnlessIn("[options] FROM_LINK TO_LINK", help)
599 self.failUnlessIn("Use 'tahoe ln' to duplicate a link", help)
601 def test_mkdir(self):
602 help = str(cli.MakeDirectoryOptions())
603 self.failUnlessIn("[options] [REMOTE_DIR]", help)
604 self.failUnlessIn("Create a new directory", help)
606 def test_backup(self):
607 help = str(cli.BackupOptions())
608 self.failUnlessIn("[options] FROM ALIAS:TO", help)
610 def test_webopen(self):
611 help = str(cli.WebopenOptions())
612 self.failUnlessIn("[options] [ALIAS:PATH]", help)
614 def test_manifest(self):
615 help = str(cli.ManifestOptions())
616 self.failUnlessIn("[options] [ALIAS:PATH]", help)
618 def test_stats(self):
619 help = str(cli.StatsOptions())
620 self.failUnlessIn("[options] [ALIAS:PATH]", help)
622 def test_check(self):
623 help = str(cli.CheckOptions())
624 self.failUnlessIn("[options] [ALIAS:PATH]", help)
626 def test_deep_check(self):
627 help = str(cli.DeepCheckOptions())
628 self.failUnlessIn("[options] [ALIAS:PATH]", help)
630 def test_create_alias(self):
631 help = str(cli.CreateAliasOptions())
632 self.failUnlessIn("[options] ALIAS[:]", help)
634 def test_add_alias(self):
635 help = str(cli.AddAliasOptions())
636 self.failUnlessIn("[options] ALIAS[:] DIRCAP", help)
638 def test_list_aliases(self):
639 help = str(cli.ListAliasesOptions())
640 self.failUnlessIn("[options]", help)
642 def test_start(self):
643 help = str(startstop_node.StartOptions())
644 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
647 help = str(startstop_node.StopOptions())
648 self.failUnlessIn("[options] [NODEDIR]", help)
650 def test_restart(self):
651 help = str(startstop_node.RestartOptions())
652 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
655 help = str(startstop_node.RunOptions())
656 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
658 def test_create_client(self):
659 help = str(create_node.CreateClientOptions())
660 self.failUnlessIn("[options] [NODEDIR]", help)
662 def test_create_node(self):
663 help = str(create_node.CreateNodeOptions())
664 self.failUnlessIn("[options] [NODEDIR]", help)
666 def test_create_introducer(self):
667 help = str(create_node.CreateIntroducerOptions())
668 self.failUnlessIn("[options] NODEDIR", help)
670 def test_debug_trial(self):
671 help = str(debug.TrialOptions())
672 self.failUnlessIn(" [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help)
673 self.failUnlessIn("The 'tahoe debug trial' command uses the correct imports", help)
675 def test_debug_flogtool(self):
676 options = debug.FlogtoolOptions()
678 self.failUnlessIn(" [global-options] debug flogtool ", help)
679 self.failUnlessIn("The 'tahoe debug flogtool' command uses the correct imports", help)
681 for (option, shortcut, oClass, desc) in options.subCommands:
682 subhelp = str(oClass())
683 self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp)
686 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):
687 def _create_test_file(self):
688 data = "puppies" * 1000
689 path = os.path.join(self.basedir, "datafile")
690 fileutil.write(path, data)
693 def test_ln_without_alias(self):
694 # if invoked without an alias when the 'tahoe' alias doesn't
695 # exist, 'tahoe ln' should output a useful error message and not
697 self.basedir = "cli/Ln/ln_without_alias"
699 d = self.do_cli("ln", "from", "to")
700 def _check((rc, out, err)):
701 self.failUnlessReallyEqual(rc, 1)
702 self.failUnlessIn("error:", err)
703 self.failUnlessReallyEqual(out, "")
704 d.addCallback(_check)
705 # Make sure that validation extends to the "to" parameter
706 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
707 d.addCallback(lambda ign: self._create_test_file())
708 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
710 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "to"))
711 d.addCallback(_check)
714 def test_ln_with_nonexistent_alias(self):
715 # If invoked with aliases that don't exist, 'tahoe ln' should
716 # output a useful error message and not a stack trace.
717 self.basedir = "cli/Ln/ln_with_nonexistent_alias"
719 d = self.do_cli("ln", "havasu:from", "havasu:to")
720 def _check((rc, out, err)):
721 self.failUnlessReallyEqual(rc, 1)
722 self.failUnlessIn("error:", err)
723 d.addCallback(_check)
724 # Make sure that validation occurs on the to parameter if the
725 # from parameter passes.
726 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
727 d.addCallback(lambda ign: self._create_test_file())
728 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
730 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "huron:to"))
731 d.addCallback(_check)
735 class Admin(unittest.TestCase):
736 def do_cli(self, *args, **kwargs):
738 stdin = kwargs.get("stdin", "")
739 stdout, stderr = StringIO(), StringIO()
740 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
741 stdin=StringIO(stdin),
742 stdout=stdout, stderr=stderr)
744 return stdout.getvalue(), stderr.getvalue()
748 def test_generate_keypair(self):
749 d = self.do_cli("admin", "generate-keypair")
750 def _done( (stdout, stderr) ):
751 lines = [line.strip() for line in stdout.splitlines()]
752 privkey_bits = lines[0].split()
753 pubkey_bits = lines[1].split()
754 sk_header = "private:"
755 vk_header = "public:"
756 self.failUnlessEqual(privkey_bits[0], sk_header, lines[0])
757 self.failUnlessEqual(pubkey_bits[0], vk_header, lines[1])
758 self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0])
759 self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1])
760 sk_bytes = base32.a2b(keyutil.remove_prefix(privkey_bits[1], "priv-v0-"))
761 sk = ed25519.SigningKey(sk_bytes)
762 vk_bytes = base32.a2b(keyutil.remove_prefix(pubkey_bits[1], "pub-v0-"))
763 self.failUnlessEqual(sk.get_verifying_key_bytes(), vk_bytes)
767 def test_derive_pubkey(self):
768 priv1,pub1 = keyutil.make_keypair()
769 d = self.do_cli("admin", "derive-pubkey", priv1)
770 def _done( (stdout, stderr) ):
771 lines = stdout.split("\n")
772 privkey_line = lines[0].strip()
773 pubkey_line = lines[1].strip()
774 sk_header = "private: priv-v0-"
775 vk_header = "public: pub-v0-"
776 self.failUnless(privkey_line.startswith(sk_header), privkey_line)
777 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line)
778 pub2 = pubkey_line[len(vk_header):]
779 self.failUnlessEqual("pub-v0-"+pub2, pub1)
784 class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
786 self.basedir = "cli/Errors/get"
788 c0 = self.g.clients[0]
791 d = c0.upload(upload.Data(DATA, convergence=""))
793 self.uri_1share = ur.get_uri()
794 self.delete_shares_numbered(ur.get_uri(), range(1,10))
795 d.addCallback(_stash_bad)
797 # the download is abandoned as soon as it's clear that we won't get
798 # enough shares. The one remaining share might be in either the
799 # COMPLETE or the PENDING state.
800 in_complete_msg = "ran out of shares: complete=sh0 pending= overdue= unused= need 3"
801 in_pending_msg = "ran out of shares: complete= pending=Share(sh0-on-fob7vqgd) overdue= unused= need 3"
803 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share))
804 def _check1((rc, out, err)):
805 self.failIfEqual(rc, 0)
806 self.failUnless("410 Gone" in err, err)
807 self.failUnlessIn("NotEnoughSharesError: ", err)
808 self.failUnless(in_complete_msg in err or in_pending_msg in err,
810 d.addCallback(_check1)
812 targetf = os.path.join(self.basedir, "output")
813 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share, targetf))
814 def _check2((rc, out, err)):
815 self.failIfEqual(rc, 0)
816 self.failUnless("410 Gone" in err, err)
817 self.failUnlessIn("NotEnoughSharesError: ", err)
818 self.failUnless(in_complete_msg in err or in_pending_msg in err,
820 self.failIf(os.path.exists(targetf))
821 d.addCallback(_check2)
825 def test_broken_socket(self):
826 # When the http connection breaks (such as when node.url is overwritten
827 # by a confused user), a user friendly error message should be printed.
828 self.basedir = "cli/Errors/test_broken_socket"
831 # Simulate a connection error
832 def _socket_error(*args, **kwargs):
833 raise socket_error('test error')
834 self.patch(allmydata.scripts.common_http.httplib.HTTPConnection,
835 "endheaders", _socket_error)
837 d = self.do_cli("mkdir")
838 def _check_invalid((rc,stdout,stderr)):
839 self.failIfEqual(rc, 0)
840 self.failUnlessIn("Error trying to connect to http://127.0.0.1", stderr)
841 d.addCallback(_check_invalid)
845 class Get(GridTestMixin, CLITestMixin, unittest.TestCase):
846 def test_get_without_alias(self):
847 # 'tahoe get' should output a useful error message when invoked
848 # without an explicit alias and when the default 'tahoe' alias
849 # hasn't been created yet.
850 self.basedir = "cli/Get/get_without_alias"
852 d = self.do_cli('get', 'file')
853 def _check((rc, out, err)):
854 self.failUnlessReallyEqual(rc, 1)
855 self.failUnlessIn("error:", err)
856 self.failUnlessReallyEqual(out, "")
857 d.addCallback(_check)
860 def test_get_with_nonexistent_alias(self):
861 # 'tahoe get' should output a useful error message when invoked with
862 # an explicit alias that doesn't exist.
863 self.basedir = "cli/Get/get_with_nonexistent_alias"
865 d = self.do_cli("get", "nonexistent:file")
866 def _check((rc, out, err)):
867 self.failUnlessReallyEqual(rc, 1)
868 self.failUnlessIn("error:", err)
869 self.failUnlessIn("nonexistent", err)
870 self.failUnlessReallyEqual(out, "")
871 d.addCallback(_check)
875 class Manifest(GridTestMixin, CLITestMixin, unittest.TestCase):
876 def test_manifest_without_alias(self):
877 # 'tahoe manifest' should output a useful error message when invoked
878 # without an explicit alias when the default 'tahoe' alias is
880 self.basedir = "cli/Manifest/manifest_without_alias"
882 d = self.do_cli("manifest")
883 def _check((rc, out, err)):
884 self.failUnlessReallyEqual(rc, 1)
885 self.failUnlessIn("error:", err)
886 self.failUnlessReallyEqual(out, "")
887 d.addCallback(_check)
890 def test_manifest_with_nonexistent_alias(self):
891 # 'tahoe manifest' should output a useful error message when invoked
892 # with an explicit alias that doesn't exist.
893 self.basedir = "cli/Manifest/manifest_with_nonexistent_alias"
895 d = self.do_cli("manifest", "nonexistent:")
896 def _check((rc, out, err)):
897 self.failUnlessReallyEqual(rc, 1)
898 self.failUnlessIn("error:", err)
899 self.failUnlessIn("nonexistent", err)
900 self.failUnlessReallyEqual(out, "")
901 d.addCallback(_check)
905 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase):
906 def test_mkdir(self):
907 self.basedir = os.path.dirname(self.mktemp())
910 d = self.do_cli("create-alias", "tahoe")
911 d.addCallback(lambda res: self.do_cli("mkdir", "test"))
912 def _check((rc, out, err)):
913 self.failUnlessReallyEqual(rc, 0)
914 self.failUnlessReallyEqual(err, "")
915 self.failUnlessIn("URI:", out)
916 d.addCallback(_check)
920 def test_mkdir_mutable_type(self):
921 self.basedir = os.path.dirname(self.mktemp())
923 d = self.do_cli("create-alias", "tahoe")
924 def _check((rc, out, err), st):
925 self.failUnlessReallyEqual(rc, 0)
926 self.failUnlessReallyEqual(err, "")
927 self.failUnlessIn(st, out)
929 def _mkdir(ign, mutable_type, uri_prefix, dirname):
930 d2 = self.do_cli("mkdir", "--format="+mutable_type, dirname)
931 d2.addCallback(_check, uri_prefix)
932 def _stash_filecap(cap):
933 u = uri.from_string(cap)
934 fn_uri = u.get_filenode_cap()
935 self._filecap = fn_uri.to_string()
936 d2.addCallback(_stash_filecap)
937 d2.addCallback(lambda ign: self.do_cli("ls", "--json", dirname))
938 d2.addCallback(_check, uri_prefix)
939 d2.addCallback(lambda ign: self.do_cli("ls", "--json", self._filecap))
940 d2.addCallback(_check, '"format": "%s"' % (mutable_type.upper(),))
943 d.addCallback(_mkdir, "sdmf", "URI:DIR2", "tahoe:foo")
944 d.addCallback(_mkdir, "SDMF", "URI:DIR2", "tahoe:foo2")
945 d.addCallback(_mkdir, "mdmf", "URI:DIR2-MDMF", "tahoe:bar")
946 d.addCallback(_mkdir, "MDMF", "URI:DIR2-MDMF", "tahoe:bar2")
949 def test_mkdir_mutable_type_unlinked(self):
950 self.basedir = os.path.dirname(self.mktemp())
952 d = self.do_cli("mkdir", "--format=SDMF")
953 def _check((rc, out, err), st):
954 self.failUnlessReallyEqual(rc, 0)
955 self.failUnlessReallyEqual(err, "")
956 self.failUnlessIn(st, out)
958 d.addCallback(_check, "URI:DIR2")
959 def _stash_dircap(cap):
961 # Now we're going to feed the cap into uri.from_string...
962 u = uri.from_string(cap)
963 # ...grab the underlying filenode uri.
964 fn_uri = u.get_filenode_cap()
966 self._filecap = fn_uri.to_string()
967 d.addCallback(_stash_dircap)
968 d.addCallback(lambda res: self.do_cli("ls", "--json",
970 d.addCallback(_check, '"format": "SDMF"')
971 d.addCallback(lambda res: self.do_cli("mkdir", "--format=MDMF"))
972 d.addCallback(_check, "URI:DIR2-MDMF")
973 d.addCallback(_stash_dircap)
974 d.addCallback(lambda res: self.do_cli("ls", "--json",
976 d.addCallback(_check, '"format": "MDMF"')
979 def test_mkdir_bad_mutable_type(self):
980 o = cli.MakeDirectoryOptions()
981 self.failUnlessRaises(usage.UsageError,
985 def test_mkdir_unicode(self):
986 self.basedir = os.path.dirname(self.mktemp())
990 motorhead_arg = u"tahoe:Mot\u00F6rhead".encode(get_io_encoding())
991 except UnicodeEncodeError:
992 raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
994 d = self.do_cli("create-alias", "tahoe")
995 d.addCallback(lambda res: self.do_cli("mkdir", motorhead_arg))
996 def _check((rc, out, err)):
997 self.failUnlessReallyEqual(rc, 0)
998 self.failUnlessReallyEqual(err, "")
999 self.failUnlessIn("URI:", out)
1000 d.addCallback(_check)
1004 def test_mkdir_with_nonexistent_alias(self):
1005 # when invoked with an alias that doesn't exist, 'tahoe mkdir' should
1006 # output a sensible error message rather than a stack trace.
1007 self.basedir = "cli/Mkdir/mkdir_with_nonexistent_alias"
1009 d = self.do_cli("mkdir", "havasu:")
1010 def _check((rc, out, err)):
1011 self.failUnlessReallyEqual(rc, 1)
1012 self.failUnlessIn("error:", err)
1013 self.failUnlessReallyEqual(out, "")
1014 d.addCallback(_check)
1018 class Unlink(GridTestMixin, CLITestMixin, unittest.TestCase):
1021 def _create_test_file(self):
1022 data = "puppies" * 1000
1023 path = os.path.join(self.basedir, "datafile")
1024 fileutil.write(path, data)
1025 self.datafile = path
1027 def test_unlink_without_alias(self):
1028 # 'tahoe unlink' should behave sensibly when invoked without an explicit
1029 # alias before the default 'tahoe' alias has been created.
1030 self.basedir = "cli/Unlink/%s_without_alias" % (self.command,)
1032 d = self.do_cli(self.command, "afile")
1033 def _check((rc, out, err)):
1034 self.failUnlessReallyEqual(rc, 1)
1035 self.failUnlessIn("error:", err)
1036 self.failUnlessReallyEqual(out, "")
1037 d.addCallback(_check)
1039 d.addCallback(lambda ign: self.do_cli(self.command, "afile"))
1040 d.addCallback(_check)
1043 def test_unlink_with_nonexistent_alias(self):
1044 # 'tahoe unlink' should behave sensibly when invoked with an explicit
1045 # alias that doesn't exist.
1046 self.basedir = "cli/Unlink/%s_with_nonexistent_alias" % (self.command,)
1048 d = self.do_cli(self.command, "nonexistent:afile")
1049 def _check((rc, out, err)):
1050 self.failUnlessReallyEqual(rc, 1)
1051 self.failUnlessIn("error:", err)
1052 self.failUnlessIn("nonexistent", err)
1053 self.failUnlessReallyEqual(out, "")
1054 d.addCallback(_check)
1056 d.addCallback(lambda ign: self.do_cli(self.command, "nonexistent:afile"))
1057 d.addCallback(_check)
1060 def test_unlink_without_path(self):
1061 # 'tahoe unlink' should give a sensible error message when invoked without a path.
1062 self.basedir = "cli/Unlink/%s_without_path" % (self.command,)
1064 self._create_test_file()
1065 d = self.do_cli("create-alias", "tahoe")
1066 d.addCallback(lambda ign: self.do_cli("put", self.datafile, "tahoe:test"))
1067 def _do_unlink((rc, out, err)):
1068 self.failUnlessReallyEqual(rc, 0)
1069 self.failUnless(out.startswith("URI:"), out)
1070 return self.do_cli(self.command, out.strip('\n'))
1071 d.addCallback(_do_unlink)
1073 def _check((rc, out, err)):
1074 self.failUnlessReallyEqual(rc, 1)
1075 self.failUnlessIn("'tahoe %s'" % (self.command,), err)
1076 self.failUnlessIn("path must be given", err)
1077 self.failUnlessReallyEqual(out, "")
1078 d.addCallback(_check)
1083 """Test that 'tahoe rm' behaves in the same way as 'tahoe unlink'."""
1087 class Stats(GridTestMixin, CLITestMixin, unittest.TestCase):
1088 def test_empty_directory(self):
1089 self.basedir = "cli/Stats/empty_directory"
1091 c0 = self.g.clients[0]
1093 d = c0.create_dirnode()
1096 self.rooturi = n.get_uri()
1097 d.addCallback(_stash_root)
1099 # make sure we can get stats on an empty directory too
1100 d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
1101 def _check_stats((rc, out, err)):
1102 self.failUnlessReallyEqual(err, "")
1103 self.failUnlessReallyEqual(rc, 0)
1104 lines = out.splitlines()
1105 self.failUnlessIn(" count-immutable-files: 0", lines)
1106 self.failUnlessIn(" count-mutable-files: 0", lines)
1107 self.failUnlessIn(" count-literal-files: 0", lines)
1108 self.failUnlessIn(" count-directories: 1", lines)
1109 self.failUnlessIn(" size-immutable-files: 0", lines)
1110 self.failIfIn("Size Histogram:", lines)
1111 d.addCallback(_check_stats)
1115 def test_stats_without_alias(self):
1116 # when invoked with no explicit alias and before the default 'tahoe'
1117 # alias is created, 'tahoe stats' should output an informative error
1118 # message, not a stack trace.
1119 self.basedir = "cli/Stats/stats_without_alias"
1121 d = self.do_cli("stats")
1122 def _check((rc, out, err)):
1123 self.failUnlessReallyEqual(rc, 1)
1124 self.failUnlessIn("error:", err)
1125 self.failUnlessReallyEqual(out, "")
1126 d.addCallback(_check)
1129 def test_stats_with_nonexistent_alias(self):
1130 # when invoked with an explicit alias that doesn't exist,
1131 # 'tahoe stats' should output a useful error message.
1132 self.basedir = "cli/Stats/stats_with_nonexistent_alias"
1134 d = self.do_cli("stats", "havasu:")
1135 def _check((rc, out, err)):
1136 self.failUnlessReallyEqual(rc, 1)
1137 self.failUnlessIn("error:", err)
1138 self.failUnlessReallyEqual(out, "")
1139 d.addCallback(_check)
1143 class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase):
1144 def test_webopen_with_nonexistent_alias(self):
1145 # when invoked with an alias that doesn't exist, 'tahoe webopen'
1146 # should output an informative error message instead of a stack
1148 self.basedir = "cli/Webopen/webopen_with_nonexistent_alias"
1150 d = self.do_cli("webopen", "fake:")
1151 def _check((rc, out, err)):
1152 self.failUnlessReallyEqual(rc, 1)
1153 self.failUnlessIn("error:", err)
1154 self.failUnlessReallyEqual(out, "")
1155 d.addCallback(_check)
1158 def test_webopen(self):
1159 # TODO: replace with @patch that supports Deferreds.
1161 def call_webbrowser_open(url):
1162 self.failUnlessIn(self.alias_uri.replace(':', '%3A'), url)
1163 self.webbrowser_open_called = True
1165 webbrowser.open = self.old_webbrowser_open
1168 self.old_webbrowser_open = webbrowser.open
1170 webbrowser.open = call_webbrowser_open
1172 self.basedir = "cli/Webopen/webopen"
1174 d = self.do_cli("create-alias", "alias:")
1175 def _check_alias((rc, out, err)):
1176 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1177 self.failUnlessIn("Alias 'alias' created", out)
1178 self.failUnlessReallyEqual(err, "")
1179 self.alias_uri = get_aliases(self.get_clientdir())["alias"]
1180 d.addCallback(_check_alias)
1181 d.addCallback(lambda res: self.do_cli("webopen", "alias:"))
1182 def _check_webopen((rc, out, err)):
1183 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1184 self.failUnlessReallyEqual(out, "")
1185 self.failUnlessReallyEqual(err, "")
1186 self.failUnless(self.webbrowser_open_called)
1187 d.addCallback(_check_webopen)
1194 class Options(ReallyEqualMixin, unittest.TestCase):
1195 # this test case only looks at argument-processing and simple stuff.
1197 def parse(self, args, stdout=None):
1198 o = runner.Options()
1199 if stdout is not None:
1201 o.parseOptions(args)
1202 while hasattr(o, "subOptions"):
1206 def test_list(self):
1207 fileutil.rm_dir("cli/test_options")
1208 fileutil.make_dirs("cli/test_options")
1209 fileutil.make_dirs("cli/test_options/private")
1210 fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n")
1211 filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16,
1212 fingerprint="\x00"*32)
1213 private_uri = uri.DirectoryURI(filenode_uri).to_string()
1214 fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n")
1215 def parse2(args): return parse_options("cli/test_options", "ls", args)
1217 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1218 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1219 self.failUnlessEqual(o.where, u"")
1221 o = parse2(["--node-url", "http://example.org:8111/"])
1222 self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
1223 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1224 self.failUnlessEqual(o.where, u"")
1226 o = parse2(["--dir-cap", "root"])
1227 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1228 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
1229 self.failUnlessEqual(o.where, u"")
1231 other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
1232 fingerprint="\x11"*32)
1233 other_uri = uri.DirectoryURI(other_filenode_uri).to_string()
1234 o = parse2(["--dir-cap", other_uri])
1235 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1236 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1237 self.failUnlessEqual(o.where, u"")
1239 o = parse2(["--dir-cap", other_uri, "subdir"])
1240 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1241 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1242 self.failUnlessEqual(o.where, u"subdir")
1244 self.failUnlessRaises(usage.UsageError, parse2,
1245 ["--node-url", "NOT-A-URL"])
1247 o = parse2(["--node-url", "http://localhost:8080"])
1248 self.failUnlessEqual(o["node-url"], "http://localhost:8080/")
1250 o = parse2(["--node-url", "https://localhost/"])
1251 self.failUnlessEqual(o["node-url"], "https://localhost/")
1253 def test_version(self):
1254 # "tahoe --version" dumps text to stdout and exits
1256 self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout)
1257 self.failUnlessIn(allmydata.__appname__ + ":", stdout.getvalue())
1258 # but "tahoe SUBCOMMAND --version" should be rejected
1259 self.failUnlessRaises(usage.UsageError, self.parse,
1260 ["start", "--version"])
1261 self.failUnlessRaises(usage.UsageError, self.parse,
1262 ["start", "--version-and-path"])
1264 def test_quiet(self):
1265 # accepted as an overall option, but not on subcommands
1266 o = self.parse(["--quiet", "start"])
1267 self.failUnless(o.parent["quiet"])
1268 self.failUnlessRaises(usage.UsageError, self.parse,
1269 ["start", "--quiet"])
1271 def test_basedir(self):
1272 # accept a --node-directory option before the verb, or a --basedir
1273 # option after, or a basedir argument after, but none in the wrong
1274 # place, and not more than one of the three.
1275 o = self.parse(["start"])
1276 self.failUnlessReallyEqual(o["basedir"], os.path.join(fileutil.abspath_expanduser_unicode(u"~"),
1278 o = self.parse(["start", "here"])
1279 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1280 o = self.parse(["start", "--basedir", "there"])
1281 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1282 o = self.parse(["--node-directory", "there", "start"])
1283 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1285 o = self.parse(["start", "here", "--nodaemon"])
1286 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1288 self.failUnlessRaises(usage.UsageError, self.parse,
1289 ["--basedir", "there", "start"])
1290 self.failUnlessRaises(usage.UsageError, self.parse,
1291 ["start", "--node-directory", "there"])
1293 self.failUnlessRaises(usage.UsageError, self.parse,
1294 ["--node-directory=there",
1295 "start", "--basedir=here"])
1296 self.failUnlessRaises(usage.UsageError, self.parse,
1297 ["start", "--basedir=here", "anywhere"])
1298 self.failUnlessRaises(usage.UsageError, self.parse,
1299 ["--node-directory=there",
1300 "start", "anywhere"])
1301 self.failUnlessRaises(usage.UsageError, self.parse,
1302 ["--node-directory=there",
1303 "start", "--basedir=here", "anywhere"])
1305 self.failUnlessRaises(usage.UsageError, self.parse,
1306 ["--node-directory=there", "start", "--nodaemon"])
1307 self.failUnlessRaises(usage.UsageError, self.parse,
1308 ["start", "--basedir=here", "--nodaemon"])