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):
564 def failUnlessInNormalized(self, x, y):
565 # helper function to deal with the --help output being wrapped to
566 # various widths, depending on the $COLUMNS environment variable
567 self.failUnlessIn(x.replace("\n", " "), y.replace("\n", " "))
570 help = str(cli.GetOptions())
571 self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help)
572 self.failUnlessIn("% tahoe get FOO |less", help)
575 help = str(cli.PutOptions())
576 self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help)
577 self.failUnlessIn("% cat FILE | tahoe put", help)
580 help = str(cli.ListOptions())
581 self.failUnlessIn("[options] [PATH]", help)
583 def test_unlink(self):
584 help = str(cli.UnlinkOptions())
585 self.failUnlessIn("[options] REMOTE_FILE", help)
588 help = str(cli.RmOptions())
589 self.failUnlessIn("[options] REMOTE_FILE", help)
592 help = str(cli.MvOptions())
593 self.failUnlessIn("[options] FROM TO", help)
594 self.failUnlessInNormalized("Use 'tahoe mv' to move files", help)
597 help = str(cli.CpOptions())
598 self.failUnlessIn("[options] FROM.. TO", help)
599 self.failUnlessInNormalized("Use 'tahoe cp' to copy files", help)
602 help = str(cli.LnOptions())
603 self.failUnlessIn("[options] FROM_LINK TO_LINK", help)
604 self.failUnlessInNormalized("Use 'tahoe ln' to duplicate a link", help)
606 def test_mkdir(self):
607 help = str(cli.MakeDirectoryOptions())
608 self.failUnlessIn("[options] [REMOTE_DIR]", help)
609 self.failUnlessInNormalized("Create a new directory", help)
611 def test_backup(self):
612 help = str(cli.BackupOptions())
613 self.failUnlessIn("[options] FROM ALIAS:TO", help)
615 def test_webopen(self):
616 help = str(cli.WebopenOptions())
617 self.failUnlessIn("[options] [ALIAS:PATH]", help)
619 def test_manifest(self):
620 help = str(cli.ManifestOptions())
621 self.failUnlessIn("[options] [ALIAS:PATH]", help)
623 def test_stats(self):
624 help = str(cli.StatsOptions())
625 self.failUnlessIn("[options] [ALIAS:PATH]", help)
627 def test_check(self):
628 help = str(cli.CheckOptions())
629 self.failUnlessIn("[options] [ALIAS:PATH]", help)
631 def test_deep_check(self):
632 help = str(cli.DeepCheckOptions())
633 self.failUnlessIn("[options] [ALIAS:PATH]", help)
635 def test_create_alias(self):
636 help = str(cli.CreateAliasOptions())
637 self.failUnlessIn("[options] ALIAS[:]", help)
639 def test_add_alias(self):
640 help = str(cli.AddAliasOptions())
641 self.failUnlessIn("[options] ALIAS[:] DIRCAP", help)
643 def test_list_aliases(self):
644 help = str(cli.ListAliasesOptions())
645 self.failUnlessIn("[options]", help)
647 def test_start(self):
648 help = str(startstop_node.StartOptions())
649 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
652 help = str(startstop_node.StopOptions())
653 self.failUnlessIn("[options] [NODEDIR]", help)
655 def test_restart(self):
656 help = str(startstop_node.RestartOptions())
657 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
660 help = str(startstop_node.RunOptions())
661 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
663 def test_create_client(self):
664 help = str(create_node.CreateClientOptions())
665 self.failUnlessIn("[options] [NODEDIR]", help)
667 def test_create_node(self):
668 help = str(create_node.CreateNodeOptions())
669 self.failUnlessIn("[options] [NODEDIR]", help)
671 def test_create_introducer(self):
672 help = str(create_node.CreateIntroducerOptions())
673 self.failUnlessIn("[options] NODEDIR", help)
675 def test_debug_trial(self):
676 help = str(debug.TrialOptions())
677 self.failUnlessIn(" [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help)
678 self.failUnlessInNormalized("The 'tahoe debug trial' command uses the correct imports", help)
680 def test_debug_flogtool(self):
681 options = debug.FlogtoolOptions()
683 self.failUnlessIn(" [global-options] debug flogtool ", help)
684 self.failUnlessInNormalized("The 'tahoe debug flogtool' command uses the correct imports", help)
686 for (option, shortcut, oClass, desc) in options.subCommands:
687 subhelp = str(oClass())
688 self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp)
691 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):
692 def _create_test_file(self):
693 data = "puppies" * 1000
694 path = os.path.join(self.basedir, "datafile")
695 fileutil.write(path, data)
698 def test_ln_without_alias(self):
699 # if invoked without an alias when the 'tahoe' alias doesn't
700 # exist, 'tahoe ln' should output a useful error message and not
702 self.basedir = "cli/Ln/ln_without_alias"
704 d = self.do_cli("ln", "from", "to")
705 def _check((rc, out, err)):
706 self.failUnlessReallyEqual(rc, 1)
707 self.failUnlessIn("error:", err)
708 self.failUnlessReallyEqual(out, "")
709 d.addCallback(_check)
710 # Make sure that validation extends to the "to" parameter
711 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
712 d.addCallback(lambda ign: self._create_test_file())
713 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
715 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "to"))
716 d.addCallback(_check)
719 def test_ln_with_nonexistent_alias(self):
720 # If invoked with aliases that don't exist, 'tahoe ln' should
721 # output a useful error message and not a stack trace.
722 self.basedir = "cli/Ln/ln_with_nonexistent_alias"
724 d = self.do_cli("ln", "havasu:from", "havasu:to")
725 def _check((rc, out, err)):
726 self.failUnlessReallyEqual(rc, 1)
727 self.failUnlessIn("error:", err)
728 d.addCallback(_check)
729 # Make sure that validation occurs on the to parameter if the
730 # from parameter passes.
731 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
732 d.addCallback(lambda ign: self._create_test_file())
733 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
735 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "huron:to"))
736 d.addCallback(_check)
740 class Admin(unittest.TestCase):
741 def do_cli(self, *args, **kwargs):
743 stdin = kwargs.get("stdin", "")
744 stdout, stderr = StringIO(), StringIO()
745 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
746 stdin=StringIO(stdin),
747 stdout=stdout, stderr=stderr)
749 return stdout.getvalue(), stderr.getvalue()
753 def test_generate_keypair(self):
754 d = self.do_cli("admin", "generate-keypair")
755 def _done( (stdout, stderr) ):
756 lines = [line.strip() for line in stdout.splitlines()]
757 privkey_bits = lines[0].split()
758 pubkey_bits = lines[1].split()
759 sk_header = "private:"
760 vk_header = "public:"
761 self.failUnlessEqual(privkey_bits[0], sk_header, lines[0])
762 self.failUnlessEqual(pubkey_bits[0], vk_header, lines[1])
763 self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0])
764 self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1])
765 sk_bytes = base32.a2b(keyutil.remove_prefix(privkey_bits[1], "priv-v0-"))
766 sk = ed25519.SigningKey(sk_bytes)
767 vk_bytes = base32.a2b(keyutil.remove_prefix(pubkey_bits[1], "pub-v0-"))
768 self.failUnlessEqual(sk.get_verifying_key_bytes(), vk_bytes)
772 def test_derive_pubkey(self):
773 priv1,pub1 = keyutil.make_keypair()
774 d = self.do_cli("admin", "derive-pubkey", priv1)
775 def _done( (stdout, stderr) ):
776 lines = stdout.split("\n")
777 privkey_line = lines[0].strip()
778 pubkey_line = lines[1].strip()
779 sk_header = "private: priv-v0-"
780 vk_header = "public: pub-v0-"
781 self.failUnless(privkey_line.startswith(sk_header), privkey_line)
782 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line)
783 pub2 = pubkey_line[len(vk_header):]
784 self.failUnlessEqual("pub-v0-"+pub2, pub1)
789 class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
791 self.basedir = "cli/Errors/get"
793 c0 = self.g.clients[0]
796 d = c0.upload(upload.Data(DATA, convergence=""))
798 self.uri_1share = ur.get_uri()
799 self.delete_shares_numbered(ur.get_uri(), range(1,10))
800 d.addCallback(_stash_bad)
802 # the download is abandoned as soon as it's clear that we won't get
803 # enough shares. The one remaining share might be in either the
804 # COMPLETE or the PENDING state.
805 in_complete_msg = "ran out of shares: complete=sh0 pending= overdue= unused= need 3"
806 in_pending_msg = "ran out of shares: complete= pending=Share(sh0-on-fob7vqgd) overdue= unused= need 3"
808 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share))
809 def _check1((rc, out, err)):
810 self.failIfEqual(rc, 0)
811 self.failUnless("410 Gone" in err, err)
812 self.failUnlessIn("NotEnoughSharesError: ", err)
813 self.failUnless(in_complete_msg in err or in_pending_msg in err,
815 d.addCallback(_check1)
817 targetf = os.path.join(self.basedir, "output")
818 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share, targetf))
819 def _check2((rc, out, err)):
820 self.failIfEqual(rc, 0)
821 self.failUnless("410 Gone" in err, err)
822 self.failUnlessIn("NotEnoughSharesError: ", err)
823 self.failUnless(in_complete_msg in err or in_pending_msg in err,
825 self.failIf(os.path.exists(targetf))
826 d.addCallback(_check2)
830 def test_broken_socket(self):
831 # When the http connection breaks (such as when node.url is overwritten
832 # by a confused user), a user friendly error message should be printed.
833 self.basedir = "cli/Errors/test_broken_socket"
836 # Simulate a connection error
837 def _socket_error(*args, **kwargs):
838 raise socket_error('test error')
839 self.patch(allmydata.scripts.common_http.httplib.HTTPConnection,
840 "endheaders", _socket_error)
842 d = self.do_cli("mkdir")
843 def _check_invalid((rc,stdout,stderr)):
844 self.failIfEqual(rc, 0)
845 self.failUnlessIn("Error trying to connect to http://127.0.0.1", stderr)
846 d.addCallback(_check_invalid)
850 class Get(GridTestMixin, CLITestMixin, unittest.TestCase):
851 def test_get_without_alias(self):
852 # 'tahoe get' should output a useful error message when invoked
853 # without an explicit alias and when the default 'tahoe' alias
854 # hasn't been created yet.
855 self.basedir = "cli/Get/get_without_alias"
857 d = self.do_cli('get', 'file')
858 def _check((rc, out, err)):
859 self.failUnlessReallyEqual(rc, 1)
860 self.failUnlessIn("error:", err)
861 self.failUnlessReallyEqual(out, "")
862 d.addCallback(_check)
865 def test_get_with_nonexistent_alias(self):
866 # 'tahoe get' should output a useful error message when invoked with
867 # an explicit alias that doesn't exist.
868 self.basedir = "cli/Get/get_with_nonexistent_alias"
870 d = self.do_cli("get", "nonexistent:file")
871 def _check((rc, out, err)):
872 self.failUnlessReallyEqual(rc, 1)
873 self.failUnlessIn("error:", err)
874 self.failUnlessIn("nonexistent", err)
875 self.failUnlessReallyEqual(out, "")
876 d.addCallback(_check)
880 class Manifest(GridTestMixin, CLITestMixin, unittest.TestCase):
881 def test_manifest_without_alias(self):
882 # 'tahoe manifest' should output a useful error message when invoked
883 # without an explicit alias when the default 'tahoe' alias is
885 self.basedir = "cli/Manifest/manifest_without_alias"
887 d = self.do_cli("manifest")
888 def _check((rc, out, err)):
889 self.failUnlessReallyEqual(rc, 1)
890 self.failUnlessIn("error:", err)
891 self.failUnlessReallyEqual(out, "")
892 d.addCallback(_check)
895 def test_manifest_with_nonexistent_alias(self):
896 # 'tahoe manifest' should output a useful error message when invoked
897 # with an explicit alias that doesn't exist.
898 self.basedir = "cli/Manifest/manifest_with_nonexistent_alias"
900 d = self.do_cli("manifest", "nonexistent:")
901 def _check((rc, out, err)):
902 self.failUnlessReallyEqual(rc, 1)
903 self.failUnlessIn("error:", err)
904 self.failUnlessIn("nonexistent", err)
905 self.failUnlessReallyEqual(out, "")
906 d.addCallback(_check)
910 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase):
911 def test_mkdir(self):
912 self.basedir = os.path.dirname(self.mktemp())
915 d = self.do_cli("create-alias", "tahoe")
916 d.addCallback(lambda res: self.do_cli("mkdir", "test"))
917 def _check((rc, out, err)):
918 self.failUnlessReallyEqual(rc, 0)
919 self.failUnlessReallyEqual(err, "")
920 self.failUnlessIn("URI:", out)
921 d.addCallback(_check)
925 def test_mkdir_mutable_type(self):
926 self.basedir = os.path.dirname(self.mktemp())
928 d = self.do_cli("create-alias", "tahoe")
929 def _check((rc, out, err), st):
930 self.failUnlessReallyEqual(rc, 0)
931 self.failUnlessReallyEqual(err, "")
932 self.failUnlessIn(st, out)
934 def _mkdir(ign, mutable_type, uri_prefix, dirname):
935 d2 = self.do_cli("mkdir", "--format="+mutable_type, dirname)
936 d2.addCallback(_check, uri_prefix)
937 def _stash_filecap(cap):
938 u = uri.from_string(cap)
939 fn_uri = u.get_filenode_cap()
940 self._filecap = fn_uri.to_string()
941 d2.addCallback(_stash_filecap)
942 d2.addCallback(lambda ign: self.do_cli("ls", "--json", dirname))
943 d2.addCallback(_check, uri_prefix)
944 d2.addCallback(lambda ign: self.do_cli("ls", "--json", self._filecap))
945 d2.addCallback(_check, '"format": "%s"' % (mutable_type.upper(),))
948 d.addCallback(_mkdir, "sdmf", "URI:DIR2", "tahoe:foo")
949 d.addCallback(_mkdir, "SDMF", "URI:DIR2", "tahoe:foo2")
950 d.addCallback(_mkdir, "mdmf", "URI:DIR2-MDMF", "tahoe:bar")
951 d.addCallback(_mkdir, "MDMF", "URI:DIR2-MDMF", "tahoe:bar2")
954 def test_mkdir_mutable_type_unlinked(self):
955 self.basedir = os.path.dirname(self.mktemp())
957 d = self.do_cli("mkdir", "--format=SDMF")
958 def _check((rc, out, err), st):
959 self.failUnlessReallyEqual(rc, 0)
960 self.failUnlessReallyEqual(err, "")
961 self.failUnlessIn(st, out)
963 d.addCallback(_check, "URI:DIR2")
964 def _stash_dircap(cap):
966 # Now we're going to feed the cap into uri.from_string...
967 u = uri.from_string(cap)
968 # ...grab the underlying filenode uri.
969 fn_uri = u.get_filenode_cap()
971 self._filecap = fn_uri.to_string()
972 d.addCallback(_stash_dircap)
973 d.addCallback(lambda res: self.do_cli("ls", "--json",
975 d.addCallback(_check, '"format": "SDMF"')
976 d.addCallback(lambda res: self.do_cli("mkdir", "--format=MDMF"))
977 d.addCallback(_check, "URI:DIR2-MDMF")
978 d.addCallback(_stash_dircap)
979 d.addCallback(lambda res: self.do_cli("ls", "--json",
981 d.addCallback(_check, '"format": "MDMF"')
984 def test_mkdir_bad_mutable_type(self):
985 o = cli.MakeDirectoryOptions()
986 self.failUnlessRaises(usage.UsageError,
990 def test_mkdir_unicode(self):
991 self.basedir = os.path.dirname(self.mktemp())
995 motorhead_arg = u"tahoe:Mot\u00F6rhead".encode(get_io_encoding())
996 except UnicodeEncodeError:
997 raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
999 d = self.do_cli("create-alias", "tahoe")
1000 d.addCallback(lambda res: self.do_cli("mkdir", motorhead_arg))
1001 def _check((rc, out, err)):
1002 self.failUnlessReallyEqual(rc, 0)
1003 self.failUnlessReallyEqual(err, "")
1004 self.failUnlessIn("URI:", out)
1005 d.addCallback(_check)
1009 def test_mkdir_with_nonexistent_alias(self):
1010 # when invoked with an alias that doesn't exist, 'tahoe mkdir' should
1011 # output a sensible error message rather than a stack trace.
1012 self.basedir = "cli/Mkdir/mkdir_with_nonexistent_alias"
1014 d = self.do_cli("mkdir", "havasu:")
1015 def _check((rc, out, err)):
1016 self.failUnlessReallyEqual(rc, 1)
1017 self.failUnlessIn("error:", err)
1018 self.failUnlessReallyEqual(out, "")
1019 d.addCallback(_check)
1023 class Unlink(GridTestMixin, CLITestMixin, unittest.TestCase):
1026 def _create_test_file(self):
1027 data = "puppies" * 1000
1028 path = os.path.join(self.basedir, "datafile")
1029 fileutil.write(path, data)
1030 self.datafile = path
1032 def test_unlink_without_alias(self):
1033 # 'tahoe unlink' should behave sensibly when invoked without an explicit
1034 # alias before the default 'tahoe' alias has been created.
1035 self.basedir = "cli/Unlink/%s_without_alias" % (self.command,)
1037 d = self.do_cli(self.command, "afile")
1038 def _check((rc, out, err)):
1039 self.failUnlessReallyEqual(rc, 1)
1040 self.failUnlessIn("error:", err)
1041 self.failUnlessReallyEqual(out, "")
1042 d.addCallback(_check)
1044 d.addCallback(lambda ign: self.do_cli(self.command, "afile"))
1045 d.addCallback(_check)
1048 def test_unlink_with_nonexistent_alias(self):
1049 # 'tahoe unlink' should behave sensibly when invoked with an explicit
1050 # alias that doesn't exist.
1051 self.basedir = "cli/Unlink/%s_with_nonexistent_alias" % (self.command,)
1053 d = self.do_cli(self.command, "nonexistent:afile")
1054 def _check((rc, out, err)):
1055 self.failUnlessReallyEqual(rc, 1)
1056 self.failUnlessIn("error:", err)
1057 self.failUnlessIn("nonexistent", err)
1058 self.failUnlessReallyEqual(out, "")
1059 d.addCallback(_check)
1061 d.addCallback(lambda ign: self.do_cli(self.command, "nonexistent:afile"))
1062 d.addCallback(_check)
1065 def test_unlink_without_path(self):
1066 # 'tahoe unlink' should give a sensible error message when invoked without a path.
1067 self.basedir = "cli/Unlink/%s_without_path" % (self.command,)
1069 self._create_test_file()
1070 d = self.do_cli("create-alias", "tahoe")
1071 d.addCallback(lambda ign: self.do_cli("put", self.datafile, "tahoe:test"))
1072 def _do_unlink((rc, out, err)):
1073 self.failUnlessReallyEqual(rc, 0)
1074 self.failUnless(out.startswith("URI:"), out)
1075 return self.do_cli(self.command, out.strip('\n'))
1076 d.addCallback(_do_unlink)
1078 def _check((rc, out, err)):
1079 self.failUnlessReallyEqual(rc, 1)
1080 self.failUnlessIn("'tahoe %s'" % (self.command,), err)
1081 self.failUnlessIn("path must be given", err)
1082 self.failUnlessReallyEqual(out, "")
1083 d.addCallback(_check)
1088 """Test that 'tahoe rm' behaves in the same way as 'tahoe unlink'."""
1092 class Stats(GridTestMixin, CLITestMixin, unittest.TestCase):
1093 def test_empty_directory(self):
1094 self.basedir = "cli/Stats/empty_directory"
1096 c0 = self.g.clients[0]
1098 d = c0.create_dirnode()
1101 self.rooturi = n.get_uri()
1102 d.addCallback(_stash_root)
1104 # make sure we can get stats on an empty directory too
1105 d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
1106 def _check_stats((rc, out, err)):
1107 self.failUnlessReallyEqual(err, "")
1108 self.failUnlessReallyEqual(rc, 0)
1109 lines = out.splitlines()
1110 self.failUnlessIn(" count-immutable-files: 0", lines)
1111 self.failUnlessIn(" count-mutable-files: 0", lines)
1112 self.failUnlessIn(" count-literal-files: 0", lines)
1113 self.failUnlessIn(" count-directories: 1", lines)
1114 self.failUnlessIn(" size-immutable-files: 0", lines)
1115 self.failIfIn("Size Histogram:", lines)
1116 d.addCallback(_check_stats)
1120 def test_stats_without_alias(self):
1121 # when invoked with no explicit alias and before the default 'tahoe'
1122 # alias is created, 'tahoe stats' should output an informative error
1123 # message, not a stack trace.
1124 self.basedir = "cli/Stats/stats_without_alias"
1126 d = self.do_cli("stats")
1127 def _check((rc, out, err)):
1128 self.failUnlessReallyEqual(rc, 1)
1129 self.failUnlessIn("error:", err)
1130 self.failUnlessReallyEqual(out, "")
1131 d.addCallback(_check)
1134 def test_stats_with_nonexistent_alias(self):
1135 # when invoked with an explicit alias that doesn't exist,
1136 # 'tahoe stats' should output a useful error message.
1137 self.basedir = "cli/Stats/stats_with_nonexistent_alias"
1139 d = self.do_cli("stats", "havasu:")
1140 def _check((rc, out, err)):
1141 self.failUnlessReallyEqual(rc, 1)
1142 self.failUnlessIn("error:", err)
1143 self.failUnlessReallyEqual(out, "")
1144 d.addCallback(_check)
1148 class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase):
1149 def test_webopen_with_nonexistent_alias(self):
1150 # when invoked with an alias that doesn't exist, 'tahoe webopen'
1151 # should output an informative error message instead of a stack
1153 self.basedir = "cli/Webopen/webopen_with_nonexistent_alias"
1155 d = self.do_cli("webopen", "fake:")
1156 def _check((rc, out, err)):
1157 self.failUnlessReallyEqual(rc, 1)
1158 self.failUnlessIn("error:", err)
1159 self.failUnlessReallyEqual(out, "")
1160 d.addCallback(_check)
1163 def test_webopen(self):
1164 # TODO: replace with @patch that supports Deferreds.
1166 def call_webbrowser_open(url):
1167 self.failUnlessIn(self.alias_uri.replace(':', '%3A'), url)
1168 self.webbrowser_open_called = True
1170 webbrowser.open = self.old_webbrowser_open
1173 self.old_webbrowser_open = webbrowser.open
1175 webbrowser.open = call_webbrowser_open
1177 self.basedir = "cli/Webopen/webopen"
1179 d = self.do_cli("create-alias", "alias:")
1180 def _check_alias((rc, out, err)):
1181 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1182 self.failUnlessIn("Alias 'alias' created", out)
1183 self.failUnlessReallyEqual(err, "")
1184 self.alias_uri = get_aliases(self.get_clientdir())["alias"]
1185 d.addCallback(_check_alias)
1186 d.addCallback(lambda res: self.do_cli("webopen", "alias:"))
1187 def _check_webopen((rc, out, err)):
1188 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1189 self.failUnlessReallyEqual(out, "")
1190 self.failUnlessReallyEqual(err, "")
1191 self.failUnless(self.webbrowser_open_called)
1192 d.addCallback(_check_webopen)
1199 class Options(ReallyEqualMixin, unittest.TestCase):
1200 # this test case only looks at argument-processing and simple stuff.
1202 def parse(self, args, stdout=None):
1203 o = runner.Options()
1204 if stdout is not None:
1206 o.parseOptions(args)
1207 while hasattr(o, "subOptions"):
1211 def test_list(self):
1212 fileutil.rm_dir("cli/test_options")
1213 fileutil.make_dirs("cli/test_options")
1214 fileutil.make_dirs("cli/test_options/private")
1215 fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n")
1216 filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16,
1217 fingerprint="\x00"*32)
1218 private_uri = uri.DirectoryURI(filenode_uri).to_string()
1219 fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n")
1220 def parse2(args): return parse_options("cli/test_options", "ls", args)
1222 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1223 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1224 self.failUnlessEqual(o.where, u"")
1226 o = parse2(["--node-url", "http://example.org:8111/"])
1227 self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
1228 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1229 self.failUnlessEqual(o.where, u"")
1231 o = parse2(["--dir-cap", "root"])
1232 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1233 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
1234 self.failUnlessEqual(o.where, u"")
1236 other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
1237 fingerprint="\x11"*32)
1238 other_uri = uri.DirectoryURI(other_filenode_uri).to_string()
1239 o = parse2(["--dir-cap", other_uri])
1240 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1241 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1242 self.failUnlessEqual(o.where, u"")
1244 o = parse2(["--dir-cap", other_uri, "subdir"])
1245 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1246 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1247 self.failUnlessEqual(o.where, u"subdir")
1249 self.failUnlessRaises(usage.UsageError, parse2,
1250 ["--node-url", "NOT-A-URL"])
1252 o = parse2(["--node-url", "http://localhost:8080"])
1253 self.failUnlessEqual(o["node-url"], "http://localhost:8080/")
1255 o = parse2(["--node-url", "https://localhost/"])
1256 self.failUnlessEqual(o["node-url"], "https://localhost/")
1258 def test_version(self):
1259 # "tahoe --version" dumps text to stdout and exits
1261 self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout)
1262 self.failUnlessIn(allmydata.__appname__ + ":", stdout.getvalue())
1263 # but "tahoe SUBCOMMAND --version" should be rejected
1264 self.failUnlessRaises(usage.UsageError, self.parse,
1265 ["start", "--version"])
1266 self.failUnlessRaises(usage.UsageError, self.parse,
1267 ["start", "--version-and-path"])
1269 def test_quiet(self):
1270 # accepted as an overall option, but not on subcommands
1271 o = self.parse(["--quiet", "start"])
1272 self.failUnless(o.parent["quiet"])
1273 self.failUnlessRaises(usage.UsageError, self.parse,
1274 ["start", "--quiet"])
1276 def test_basedir(self):
1277 # accept a --node-directory option before the verb, or a --basedir
1278 # option after, or a basedir argument after, but none in the wrong
1279 # place, and not more than one of the three.
1280 o = self.parse(["start"])
1281 self.failUnlessReallyEqual(o["basedir"], os.path.join(fileutil.abspath_expanduser_unicode(u"~"),
1283 o = self.parse(["start", "here"])
1284 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1285 o = self.parse(["start", "--basedir", "there"])
1286 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1287 o = self.parse(["--node-directory", "there", "start"])
1288 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1290 o = self.parse(["start", "here", "--nodaemon"])
1291 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1293 self.failUnlessRaises(usage.UsageError, self.parse,
1294 ["--basedir", "there", "start"])
1295 self.failUnlessRaises(usage.UsageError, self.parse,
1296 ["start", "--node-directory", "there"])
1298 self.failUnlessRaises(usage.UsageError, self.parse,
1299 ["--node-directory=there",
1300 "start", "--basedir=here"])
1301 self.failUnlessRaises(usage.UsageError, self.parse,
1302 ["start", "--basedir=here", "anywhere"])
1303 self.failUnlessRaises(usage.UsageError, self.parse,
1304 ["--node-directory=there",
1305 "start", "anywhere"])
1306 self.failUnlessRaises(usage.UsageError, self.parse,
1307 ["--node-directory=there",
1308 "start", "--basedir=here", "anywhere"])
1310 self.failUnlessRaises(usage.UsageError, self.parse,
1311 ["--node-directory=there", "start", "--nodaemon"])
1312 self.failUnlessRaises(usage.UsageError, self.parse,
1313 ["start", "--basedir=here", "--nodaemon"])