3 from cStringIO import StringIO
6 from twisted.trial import unittest
7 from twisted.python.monkey import MonkeyPatcher
10 from allmydata.util import fileutil, hashutil, base32, keyutil
11 from allmydata.util.namespace import Namespace
12 from allmydata import uri
13 from allmydata.immutable import upload
14 from allmydata.dirnode import normalize
15 from allmydata.scripts.common_http import socket_error
16 import allmydata.scripts.common_http
17 from pycryptopp.publickey import ed25519
19 # Test that the scripts can be imported.
20 from allmydata.scripts import create_node, debug, keygen, startstop_node, \
21 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls, \
22 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen
23 _hush_pyflakes = [create_node, debug, keygen, startstop_node,
24 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls,
25 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen]
27 from allmydata.scripts import common
28 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases, get_alias, \
31 from allmydata.scripts import cli, debug, runner
32 from allmydata.test.common_util import ReallyEqualMixin
33 from allmydata.test.no_network import GridTestMixin
34 from twisted.internet import threads # CLI tests use deferToThread
35 from twisted.python import usage
37 from allmydata.util.assertutil import precondition
38 from allmydata.util.encodingutil import listdir_unicode, unicode_platform, \
39 get_io_encoding, get_filesystem_encoding, unicode_to_argv
41 timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s
43 def parse_options(basedir, command, args):
45 o.parseOptions(["--node-directory", basedir, command] + args)
46 while hasattr(o, "subOptions"):
50 class CLITestMixin(ReallyEqualMixin):
51 def do_cli(self, verb, *args, **kwargs):
52 precondition(not [True for arg in args if not isinstance(arg, str)],
53 "arguments to do_cli must be strs -- convert using unicode_to_argv", args=args)
55 # client_num is used to execute client CLI commands on a specific client.
56 client_num = kwargs.get("client_num", 0)
59 "--node-directory", unicode_to_argv(self.get_clientdir(i=client_num)),
61 argv = nodeargs + [verb] + list(args)
62 stdin = kwargs.get("stdin", "")
63 stdout, stderr = StringIO(), StringIO()
64 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
65 stdin=StringIO(stdin),
66 stdout=stdout, stderr=stderr)
68 return rc, stdout.getvalue(), stderr.getvalue()
72 def skip_if_cannot_represent_filename(self, u):
73 precondition(isinstance(u, unicode))
75 enc = get_filesystem_encoding()
76 if not unicode_platform():
79 except UnicodeEncodeError:
80 raise unittest.SkipTest("A non-ASCII filename could not be encoded on this platform.")
83 class CLI(CLITestMixin, unittest.TestCase):
84 def _dump_cap(self, *args):
85 config = debug.DumpCapOptions()
86 config.stdout,config.stderr = StringIO(), StringIO()
87 config.parseOptions(args)
88 debug.dump_cap(config)
89 self.failIf(config.stderr.getvalue())
90 output = config.stdout.getvalue()
93 def test_dump_cap_chk(self):
94 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
95 uri_extension_hash = hashutil.uri_extension_hash("stuff")
99 u = uri.CHKFileURI(key=key,
100 uri_extension_hash=uri_extension_hash,
101 needed_shares=needed_shares,
102 total_shares=total_shares,
104 output = self._dump_cap(u.to_string())
105 self.failUnless("CHK File:" in output, output)
106 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
107 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
108 self.failUnless("size: 1234" in output, output)
109 self.failUnless("k/N: 25/100" in output, output)
110 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
112 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
114 self.failUnless("client renewal secret: znxmki5zdibb5qlt46xbdvk2t55j7hibejq3i5ijyurkr6m6jkhq" in output, output)
116 output = self._dump_cap(u.get_verify_cap().to_string())
117 self.failIf("key: " in output, output)
118 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
119 self.failUnless("size: 1234" in output, output)
120 self.failUnless("k/N: 25/100" in output, output)
121 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
123 prefixed_u = "http://127.0.0.1/uri/%s" % urllib.quote(u.to_string())
124 output = self._dump_cap(prefixed_u)
125 self.failUnless("CHK File:" in output, output)
126 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
127 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
128 self.failUnless("size: 1234" in output, output)
129 self.failUnless("k/N: 25/100" in output, output)
130 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
132 def test_dump_cap_lit(self):
133 u = uri.LiteralFileURI("this is some data")
134 output = self._dump_cap(u.to_string())
135 self.failUnless("Literal File URI:" in output, output)
136 self.failUnless("data: 'this is some data'" in output, output)
138 def test_dump_cap_sdmf(self):
139 writekey = "\x01" * 16
140 fingerprint = "\xfe" * 32
141 u = uri.WriteableSSKFileURI(writekey, fingerprint)
143 output = self._dump_cap(u.to_string())
144 self.failUnless("SDMF Writeable URI:" in output, output)
145 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
146 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
147 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
148 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
150 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
152 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
154 fileutil.make_dirs("cli/test_dump_cap/private")
155 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
156 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
158 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
160 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
162 self.failIf("file renewal secret:" in output, output)
164 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
166 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
167 self.failIf("file renewal secret:" in output, output)
169 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
170 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
172 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
173 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
174 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
177 output = self._dump_cap(u.to_string())
178 self.failUnless("SDMF Read-only URI:" in output, output)
179 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
180 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
181 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
183 u = u.get_verify_cap()
184 output = self._dump_cap(u.to_string())
185 self.failUnless("SDMF Verifier URI:" in output, output)
186 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
187 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
189 def test_dump_cap_mdmf(self):
190 writekey = "\x01" * 16
191 fingerprint = "\xfe" * 32
192 u = uri.WriteableMDMFFileURI(writekey, fingerprint)
194 output = self._dump_cap(u.to_string())
195 self.failUnless("MDMF Writeable URI:" in output, output)
196 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
197 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
198 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
199 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
201 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
203 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
205 fileutil.make_dirs("cli/test_dump_cap/private")
206 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
207 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
209 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
211 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
213 self.failIf("file renewal secret:" in output, output)
215 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
217 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
218 self.failIf("file renewal secret:" in output, output)
220 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
221 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
223 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
224 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
225 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
228 output = self._dump_cap(u.to_string())
229 self.failUnless("MDMF Read-only URI:" in output, output)
230 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
231 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
232 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
234 u = u.get_verify_cap()
235 output = self._dump_cap(u.to_string())
236 self.failUnless("MDMF Verifier URI:" in output, output)
237 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
238 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
241 def test_dump_cap_chk_directory(self):
242 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
243 uri_extension_hash = hashutil.uri_extension_hash("stuff")
247 u1 = uri.CHKFileURI(key=key,
248 uri_extension_hash=uri_extension_hash,
249 needed_shares=needed_shares,
250 total_shares=total_shares,
252 u = uri.ImmutableDirectoryURI(u1)
254 output = self._dump_cap(u.to_string())
255 self.failUnless("CHK Directory URI:" in output, output)
256 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
257 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
258 self.failUnless("size: 1234" in output, output)
259 self.failUnless("k/N: 25/100" in output, output)
260 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
262 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
264 self.failUnless("file renewal secret: csrvkjgomkyyyil5yo4yk5np37p6oa2ve2hg6xmk2dy7kaxsu6xq" in output, output)
266 u = u.get_verify_cap()
267 output = self._dump_cap(u.to_string())
268 self.failUnless("CHK Directory Verifier URI:" in output, output)
269 self.failIf("key: " in output, output)
270 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
271 self.failUnless("size: 1234" in output, output)
272 self.failUnless("k/N: 25/100" in output, output)
273 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
275 def test_dump_cap_sdmf_directory(self):
276 writekey = "\x01" * 16
277 fingerprint = "\xfe" * 32
278 u1 = uri.WriteableSSKFileURI(writekey, fingerprint)
279 u = uri.DirectoryURI(u1)
281 output = self._dump_cap(u.to_string())
282 self.failUnless("Directory Writeable URI:" in output, output)
283 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
285 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
286 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
288 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
290 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
292 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
294 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
296 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
297 self.failIf("file renewal secret:" in output, output)
299 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
300 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
302 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
303 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
304 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
307 output = self._dump_cap(u.to_string())
308 self.failUnless("Directory Read-only URI:" in output, output)
309 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
310 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
311 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
313 u = u.get_verify_cap()
314 output = self._dump_cap(u.to_string())
315 self.failUnless("Directory Verifier URI:" in output, output)
316 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
317 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
319 def test_dump_cap_mdmf_directory(self):
320 writekey = "\x01" * 16
321 fingerprint = "\xfe" * 32
322 u1 = uri.WriteableMDMFFileURI(writekey, fingerprint)
323 u = uri.MDMFDirectoryURI(u1)
325 output = self._dump_cap(u.to_string())
326 self.failUnless("Directory Writeable URI:" in output, output)
327 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
329 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
330 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
332 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
334 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
336 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
338 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
340 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
341 self.failIf("file renewal secret:" in output, output)
343 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
344 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
346 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
347 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
348 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
351 output = self._dump_cap(u.to_string())
352 self.failUnless("Directory Read-only URI:" in output, output)
353 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
354 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
355 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
357 u = u.get_verify_cap()
358 output = self._dump_cap(u.to_string())
359 self.failUnless("Directory Verifier URI:" in output, output)
360 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
361 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
364 def _catalog_shares(self, *basedirs):
365 o = debug.CatalogSharesOptions()
366 o.stdout,o.stderr = StringIO(), StringIO()
367 args = list(basedirs)
369 debug.catalog_shares(o)
370 out = o.stdout.getvalue()
371 err = o.stderr.getvalue()
374 def test_catalog_shares_error(self):
375 nodedir1 = "cli/test_catalog_shares/node1"
376 sharedir = os.path.join(nodedir1, "storage", "shares", "mq", "mqfblse6m5a6dh45isu2cg7oji")
377 fileutil.make_dirs(sharedir)
378 fileutil.write("cli/test_catalog_shares/node1/storage/shares/mq/not-a-dir", "")
379 # write a bogus share that looks a little bit like CHK
380 fileutil.write(os.path.join(sharedir, "8"),
381 "\x00\x00\x00\x01" + "\xff" * 200) # this triggers an assert
383 nodedir2 = "cli/test_catalog_shares/node2"
384 fileutil.make_dirs(nodedir2)
385 fileutil.write("cli/test_catalog_shares/node1/storage/shares/not-a-dir", "")
387 # now make sure that the 'catalog-shares' commands survives the error
388 out, err = self._catalog_shares(nodedir1, nodedir2)
389 self.failUnlessReallyEqual(out, "", out)
390 self.failUnless("Error processing " in err,
391 "didn't see 'error processing' in '%s'" % err)
392 #self.failUnless(nodedir1 in err,
393 # "didn't see '%s' in '%s'" % (nodedir1, err))
394 # windows mangles the path, and os.path.join isn't enough to make
395 # up for it, so just look for individual strings
396 self.failUnless("node1" in err,
397 "didn't see 'node1' in '%s'" % err)
398 self.failUnless("mqfblse6m5a6dh45isu2cg7oji" in err,
399 "didn't see 'mqfblse6m5a6dh45isu2cg7oji' in '%s'" % err)
401 def test_alias(self):
402 def s128(c): return base32.b2a(c*(128/8))
403 def s256(c): return base32.b2a(c*(256/8))
404 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
405 WA = "URI:DIR2:%s:%s" % (s128("W"), s256("W"))
406 CA = "URI:DIR2:%s:%s" % (s128("C"), s256("C"))
407 aliases = {"tahoe": TA,
411 return get_alias(aliases, path, u"tahoe")
412 uses_lettercolon = common.platform_uses_lettercolon_drivename()
413 self.failUnlessReallyEqual(ga1(u"bare"), (TA, "bare"))
414 self.failUnlessReallyEqual(ga1(u"baredir/file"), (TA, "baredir/file"))
415 self.failUnlessReallyEqual(ga1(u"baredir/file:7"), (TA, "baredir/file:7"))
416 self.failUnlessReallyEqual(ga1(u"tahoe:"), (TA, ""))
417 self.failUnlessReallyEqual(ga1(u"tahoe:file"), (TA, "file"))
418 self.failUnlessReallyEqual(ga1(u"tahoe:dir/file"), (TA, "dir/file"))
419 self.failUnlessReallyEqual(ga1(u"work:"), (WA, ""))
420 self.failUnlessReallyEqual(ga1(u"work:file"), (WA, "file"))
421 self.failUnlessReallyEqual(ga1(u"work:dir/file"), (WA, "dir/file"))
422 # default != None means we really expect a tahoe path, regardless of
423 # whether we're on windows or not. This is what 'tahoe get' uses.
424 self.failUnlessReallyEqual(ga1(u"c:"), (CA, ""))
425 self.failUnlessReallyEqual(ga1(u"c:file"), (CA, "file"))
426 self.failUnlessReallyEqual(ga1(u"c:dir/file"), (CA, "dir/file"))
427 self.failUnlessReallyEqual(ga1(u"URI:stuff"), ("URI:stuff", ""))
428 self.failUnlessReallyEqual(ga1(u"URI:stuff/file"), ("URI:stuff", "file"))
429 self.failUnlessReallyEqual(ga1(u"URI:stuff:./file"), ("URI:stuff", "file"))
430 self.failUnlessReallyEqual(ga1(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
431 self.failUnlessReallyEqual(ga1(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
432 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:")
433 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir")
434 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir/file")
437 return get_alias(aliases, path, None)
438 self.failUnlessReallyEqual(ga2(u"bare"), (DefaultAliasMarker, "bare"))
439 self.failUnlessReallyEqual(ga2(u"baredir/file"),
440 (DefaultAliasMarker, "baredir/file"))
441 self.failUnlessReallyEqual(ga2(u"baredir/file:7"),
442 (DefaultAliasMarker, "baredir/file:7"))
443 self.failUnlessReallyEqual(ga2(u"baredir/sub:1/file:7"),
444 (DefaultAliasMarker, "baredir/sub:1/file:7"))
445 self.failUnlessReallyEqual(ga2(u"tahoe:"), (TA, ""))
446 self.failUnlessReallyEqual(ga2(u"tahoe:file"), (TA, "file"))
447 self.failUnlessReallyEqual(ga2(u"tahoe:dir/file"), (TA, "dir/file"))
448 # on windows, we really want c:foo to indicate a local file.
449 # default==None is what 'tahoe cp' uses.
451 self.failUnlessReallyEqual(ga2(u"c:"), (DefaultAliasMarker, "c:"))
452 self.failUnlessReallyEqual(ga2(u"c:file"), (DefaultAliasMarker, "c:file"))
453 self.failUnlessReallyEqual(ga2(u"c:dir/file"),
454 (DefaultAliasMarker, "c:dir/file"))
456 self.failUnlessReallyEqual(ga2(u"c:"), (CA, ""))
457 self.failUnlessReallyEqual(ga2(u"c:file"), (CA, "file"))
458 self.failUnlessReallyEqual(ga2(u"c:dir/file"), (CA, "dir/file"))
459 self.failUnlessReallyEqual(ga2(u"work:"), (WA, ""))
460 self.failUnlessReallyEqual(ga2(u"work:file"), (WA, "file"))
461 self.failUnlessReallyEqual(ga2(u"work:dir/file"), (WA, "dir/file"))
462 self.failUnlessReallyEqual(ga2(u"URI:stuff"), ("URI:stuff", ""))
463 self.failUnlessReallyEqual(ga2(u"URI:stuff/file"), ("URI:stuff", "file"))
464 self.failUnlessReallyEqual(ga2(u"URI:stuff:./file"), ("URI:stuff", "file"))
465 self.failUnlessReallyEqual(ga2(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
466 self.failUnlessReallyEqual(ga2(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
467 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:")
468 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir")
469 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir/file")
472 old = common.pretend_platform_uses_lettercolon
474 common.pretend_platform_uses_lettercolon = True
475 retval = get_alias(aliases, path, None)
477 common.pretend_platform_uses_lettercolon = old
479 self.failUnlessReallyEqual(ga3(u"bare"), (DefaultAliasMarker, "bare"))
480 self.failUnlessReallyEqual(ga3(u"baredir/file"),
481 (DefaultAliasMarker, "baredir/file"))
482 self.failUnlessReallyEqual(ga3(u"baredir/file:7"),
483 (DefaultAliasMarker, "baredir/file:7"))
484 self.failUnlessReallyEqual(ga3(u"baredir/sub:1/file:7"),
485 (DefaultAliasMarker, "baredir/sub:1/file:7"))
486 self.failUnlessReallyEqual(ga3(u"tahoe:"), (TA, ""))
487 self.failUnlessReallyEqual(ga3(u"tahoe:file"), (TA, "file"))
488 self.failUnlessReallyEqual(ga3(u"tahoe:dir/file"), (TA, "dir/file"))
489 self.failUnlessReallyEqual(ga3(u"c:"), (DefaultAliasMarker, "c:"))
490 self.failUnlessReallyEqual(ga3(u"c:file"), (DefaultAliasMarker, "c:file"))
491 self.failUnlessReallyEqual(ga3(u"c:dir/file"),
492 (DefaultAliasMarker, "c:dir/file"))
493 self.failUnlessReallyEqual(ga3(u"work:"), (WA, ""))
494 self.failUnlessReallyEqual(ga3(u"work:file"), (WA, "file"))
495 self.failUnlessReallyEqual(ga3(u"work:dir/file"), (WA, "dir/file"))
496 self.failUnlessReallyEqual(ga3(u"URI:stuff"), ("URI:stuff", ""))
497 self.failUnlessReallyEqual(ga3(u"URI:stuff:./file"), ("URI:stuff", "file"))
498 self.failUnlessReallyEqual(ga3(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
499 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:")
500 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir")
501 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir/file")
502 # calling get_alias with a path that doesn't include an alias and
503 # default set to something that isn't in the aliases argument should
504 # raise an UnknownAliasError.
506 return get_alias(aliases, path, u"badddefault:")
507 self.failUnlessRaises(common.UnknownAliasError, ga4, u"afile")
508 self.failUnlessRaises(common.UnknownAliasError, ga4, u"a/dir/path/")
511 old = common.pretend_platform_uses_lettercolon
513 common.pretend_platform_uses_lettercolon = True
514 retval = get_alias(aliases, path, u"baddefault:")
516 common.pretend_platform_uses_lettercolon = old
518 self.failUnlessRaises(common.UnknownAliasError, ga5, u"C:\\Windows")
520 def test_alias_tolerance(self):
521 def s128(c): return base32.b2a(c*(128/8))
522 def s256(c): return base32.b2a(c*(256/8))
523 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
524 aliases = {"present": TA,
525 "future": "URI-FROM-FUTURE:ooh:aah"}
527 return get_alias(aliases, path, u"tahoe")
528 self.failUnlessReallyEqual(ga1(u"present:file"), (TA, "file"))
529 # this throws, via assert IDirnodeURI.providedBy(), since get_alias()
530 # wants a dirnode, and the future cap gives us UnknownURI instead.
531 self.failUnlessRaises(AssertionError, ga1, u"future:stuff")
533 def test_listdir_unicode_good(self):
534 filenames = [u'L\u00F4zane', u'Bern', u'Gen\u00E8ve'] # must be NFC
536 for name in filenames:
537 self.skip_if_cannot_represent_filename(name)
539 basedir = "cli/common/listdir_unicode_good"
540 fileutil.make_dirs(basedir)
542 for name in filenames:
543 open(os.path.join(unicode(basedir), name), "wb").close()
545 for file in listdir_unicode(unicode(basedir)):
546 self.failUnlessIn(normalize(file), filenames)
548 def test_exception_catcher(self):
549 self.basedir = "cli/exception_catcher"
552 exc = Exception("canary")
555 ns.runner_called = False
556 def call_runner(args, install_node_control=True):
557 ns.runner_called = True
558 self.failUnlessEqual(install_node_control, True)
561 ns.sys_exit_called = False
562 def call_sys_exit(exitcode):
563 ns.sys_exit_called = True
564 self.failUnlessEqual(exitcode, 1)
566 patcher = MonkeyPatcher((runner, 'runner', call_runner),
567 (sys, 'argv', ["tahoe"]),
568 (sys, 'exit', call_sys_exit),
569 (sys, 'stderr', stderr))
570 patcher.runWithPatches(runner.run)
572 self.failUnless(ns.runner_called)
573 self.failUnless(ns.sys_exit_called)
574 self.failUnlessIn(str(exc), stderr.getvalue())
577 class Help(unittest.TestCase):
578 def failUnlessInNormalized(self, x, y):
579 # helper function to deal with the --help output being wrapped to
580 # various widths, depending on the $COLUMNS environment variable
581 self.failUnlessIn(x.replace("\n", " "), y.replace("\n", " "))
584 help = str(cli.GetOptions())
585 self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help)
586 self.failUnlessIn("% tahoe get FOO |less", help)
589 help = str(cli.PutOptions())
590 self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help)
591 self.failUnlessIn("% cat FILE | tahoe put", help)
594 help = str(cli.ListOptions())
595 self.failUnlessIn("[options] [PATH]", help)
597 def test_unlink(self):
598 help = str(cli.UnlinkOptions())
599 self.failUnlessIn("[options] REMOTE_FILE", help)
602 help = str(cli.RmOptions())
603 self.failUnlessIn("[options] REMOTE_FILE", help)
606 help = str(cli.MvOptions())
607 self.failUnlessIn("[options] FROM TO", help)
608 self.failUnlessInNormalized("Use 'tahoe mv' to move files", help)
611 help = str(cli.CpOptions())
612 self.failUnlessIn("[options] FROM.. TO", help)
613 self.failUnlessInNormalized("Use 'tahoe cp' to copy files", help)
616 help = str(cli.LnOptions())
617 self.failUnlessIn("[options] FROM_LINK TO_LINK", help)
618 self.failUnlessInNormalized("Use 'tahoe ln' to duplicate a link", help)
620 def test_mkdir(self):
621 help = str(cli.MakeDirectoryOptions())
622 self.failUnlessIn("[options] [REMOTE_DIR]", help)
623 self.failUnlessInNormalized("Create a new directory", help)
625 def test_backup(self):
626 help = str(cli.BackupOptions())
627 self.failUnlessIn("[options] FROM ALIAS:TO", help)
629 def test_webopen(self):
630 help = str(cli.WebopenOptions())
631 self.failUnlessIn("[options] [ALIAS:PATH]", help)
633 def test_manifest(self):
634 help = str(cli.ManifestOptions())
635 self.failUnlessIn("[options] [ALIAS:PATH]", help)
637 def test_stats(self):
638 help = str(cli.StatsOptions())
639 self.failUnlessIn("[options] [ALIAS:PATH]", help)
641 def test_check(self):
642 help = str(cli.CheckOptions())
643 self.failUnlessIn("[options] [ALIAS:PATH]", help)
645 def test_deep_check(self):
646 help = str(cli.DeepCheckOptions())
647 self.failUnlessIn("[options] [ALIAS:PATH]", help)
649 def test_create_alias(self):
650 help = str(cli.CreateAliasOptions())
651 self.failUnlessIn("[options] ALIAS[:]", help)
653 def test_add_alias(self):
654 help = str(cli.AddAliasOptions())
655 self.failUnlessIn("[options] ALIAS[:] DIRCAP", help)
657 def test_list_aliases(self):
658 help = str(cli.ListAliasesOptions())
659 self.failUnlessIn("[options]", help)
661 def test_start(self):
662 help = str(startstop_node.StartOptions())
663 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
666 help = str(startstop_node.StopOptions())
667 self.failUnlessIn("[options] [NODEDIR]", help)
669 def test_restart(self):
670 help = str(startstop_node.RestartOptions())
671 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
674 help = str(startstop_node.RunOptions())
675 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
677 def test_create_client(self):
678 help = str(create_node.CreateClientOptions())
679 self.failUnlessIn("[options] [NODEDIR]", help)
681 def test_create_node(self):
682 help = str(create_node.CreateNodeOptions())
683 self.failUnlessIn("[options] [NODEDIR]", help)
685 def test_create_introducer(self):
686 help = str(create_node.CreateIntroducerOptions())
687 self.failUnlessIn("[options] NODEDIR", help)
689 def test_debug_trial(self):
690 help = str(debug.TrialOptions())
691 self.failUnlessIn(" [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help)
692 self.failUnlessInNormalized("The 'tahoe debug trial' command uses the correct imports", help)
694 def test_debug_flogtool(self):
695 options = debug.FlogtoolOptions()
697 self.failUnlessIn(" [global-options] debug flogtool ", help)
698 self.failUnlessInNormalized("The 'tahoe debug flogtool' command uses the correct imports", help)
700 for (option, shortcut, oClass, desc) in options.subCommands:
701 subhelp = str(oClass())
702 self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp)
705 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):
706 def _create_test_file(self):
707 data = "puppies" * 1000
708 path = os.path.join(self.basedir, "datafile")
709 fileutil.write(path, data)
712 def test_ln_without_alias(self):
713 # if invoked without an alias when the 'tahoe' alias doesn't
714 # exist, 'tahoe ln' should output a useful error message and not
716 self.basedir = "cli/Ln/ln_without_alias"
718 d = self.do_cli("ln", "from", "to")
719 def _check((rc, out, err)):
720 self.failUnlessReallyEqual(rc, 1)
721 self.failUnlessIn("error:", err)
722 self.failUnlessReallyEqual(out, "")
723 d.addCallback(_check)
724 # Make sure that validation extends to the "to" parameter
725 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
726 d.addCallback(lambda ign: self._create_test_file())
727 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
729 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "to"))
730 d.addCallback(_check)
733 def test_ln_with_nonexistent_alias(self):
734 # If invoked with aliases that don't exist, 'tahoe ln' should
735 # output a useful error message and not a stack trace.
736 self.basedir = "cli/Ln/ln_with_nonexistent_alias"
738 d = self.do_cli("ln", "havasu:from", "havasu:to")
739 def _check((rc, out, err)):
740 self.failUnlessReallyEqual(rc, 1)
741 self.failUnlessIn("error:", err)
742 d.addCallback(_check)
743 # Make sure that validation occurs on the to parameter if the
744 # from parameter passes.
745 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
746 d.addCallback(lambda ign: self._create_test_file())
747 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
749 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "huron:to"))
750 d.addCallback(_check)
754 class Admin(unittest.TestCase):
755 def do_cli(self, *args, **kwargs):
757 stdin = kwargs.get("stdin", "")
758 stdout, stderr = StringIO(), StringIO()
759 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
760 stdin=StringIO(stdin),
761 stdout=stdout, stderr=stderr)
763 return stdout.getvalue(), stderr.getvalue()
767 def test_generate_keypair(self):
768 d = self.do_cli("admin", "generate-keypair")
769 def _done( (stdout, stderr) ):
770 lines = [line.strip() for line in stdout.splitlines()]
771 privkey_bits = lines[0].split()
772 pubkey_bits = lines[1].split()
773 sk_header = "private:"
774 vk_header = "public:"
775 self.failUnlessEqual(privkey_bits[0], sk_header, lines[0])
776 self.failUnlessEqual(pubkey_bits[0], vk_header, lines[1])
777 self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0])
778 self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1])
779 sk_bytes = base32.a2b(keyutil.remove_prefix(privkey_bits[1], "priv-v0-"))
780 sk = ed25519.SigningKey(sk_bytes)
781 vk_bytes = base32.a2b(keyutil.remove_prefix(pubkey_bits[1], "pub-v0-"))
782 self.failUnlessEqual(sk.get_verifying_key_bytes(), vk_bytes)
786 def test_derive_pubkey(self):
787 priv1,pub1 = keyutil.make_keypair()
788 d = self.do_cli("admin", "derive-pubkey", priv1)
789 def _done( (stdout, stderr) ):
790 lines = stdout.split("\n")
791 privkey_line = lines[0].strip()
792 pubkey_line = lines[1].strip()
793 sk_header = "private: priv-v0-"
794 vk_header = "public: pub-v0-"
795 self.failUnless(privkey_line.startswith(sk_header), privkey_line)
796 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line)
797 pub2 = pubkey_line[len(vk_header):]
798 self.failUnlessEqual("pub-v0-"+pub2, pub1)
803 class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
805 self.basedir = "cli/Errors/get"
807 c0 = self.g.clients[0]
810 d = c0.upload(upload.Data(DATA, convergence=""))
812 self.uri_1share = ur.get_uri()
813 self.delete_shares_numbered(ur.get_uri(), range(1,10))
814 d.addCallback(_stash_bad)
816 # the download is abandoned as soon as it's clear that we won't get
817 # enough shares. The one remaining share might be in either the
818 # COMPLETE or the PENDING state.
819 in_complete_msg = "ran out of shares: complete=sh0 pending= overdue= unused= need 3"
820 in_pending_msg = "ran out of shares: complete= pending=Share(sh0-on-fob7vqgd) overdue= unused= need 3"
822 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share))
823 def _check1((rc, out, err)):
824 self.failIfEqual(rc, 0)
825 self.failUnless("410 Gone" in err, err)
826 self.failUnlessIn("NotEnoughSharesError: ", err)
827 self.failUnless(in_complete_msg in err or in_pending_msg in err,
829 d.addCallback(_check1)
831 targetf = os.path.join(self.basedir, "output")
832 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share, targetf))
833 def _check2((rc, out, err)):
834 self.failIfEqual(rc, 0)
835 self.failUnless("410 Gone" in err, err)
836 self.failUnlessIn("NotEnoughSharesError: ", err)
837 self.failUnless(in_complete_msg in err or in_pending_msg in err,
839 self.failIf(os.path.exists(targetf))
840 d.addCallback(_check2)
844 def test_broken_socket(self):
845 # When the http connection breaks (such as when node.url is overwritten
846 # by a confused user), a user friendly error message should be printed.
847 self.basedir = "cli/Errors/test_broken_socket"
850 # Simulate a connection error
851 def _socket_error(*args, **kwargs):
852 raise socket_error('test error')
853 self.patch(allmydata.scripts.common_http.httplib.HTTPConnection,
854 "endheaders", _socket_error)
856 d = self.do_cli("mkdir")
857 def _check_invalid((rc,stdout,stderr)):
858 self.failIfEqual(rc, 0)
859 self.failUnlessIn("Error trying to connect to http://127.0.0.1", stderr)
860 d.addCallback(_check_invalid)
864 class Get(GridTestMixin, CLITestMixin, unittest.TestCase):
865 def test_get_without_alias(self):
866 # 'tahoe get' should output a useful error message when invoked
867 # without an explicit alias and when the default 'tahoe' alias
868 # hasn't been created yet.
869 self.basedir = "cli/Get/get_without_alias"
871 d = self.do_cli('get', 'file')
872 def _check((rc, out, err)):
873 self.failUnlessReallyEqual(rc, 1)
874 self.failUnlessIn("error:", err)
875 self.failUnlessReallyEqual(out, "")
876 d.addCallback(_check)
879 def test_get_with_nonexistent_alias(self):
880 # 'tahoe get' should output a useful error message when invoked with
881 # an explicit alias that doesn't exist.
882 self.basedir = "cli/Get/get_with_nonexistent_alias"
884 d = self.do_cli("get", "nonexistent:file")
885 def _check((rc, out, err)):
886 self.failUnlessReallyEqual(rc, 1)
887 self.failUnlessIn("error:", err)
888 self.failUnlessIn("nonexistent", err)
889 self.failUnlessReallyEqual(out, "")
890 d.addCallback(_check)
894 class Manifest(GridTestMixin, CLITestMixin, unittest.TestCase):
895 def test_manifest_without_alias(self):
896 # 'tahoe manifest' should output a useful error message when invoked
897 # without an explicit alias when the default 'tahoe' alias is
899 self.basedir = "cli/Manifest/manifest_without_alias"
901 d = self.do_cli("manifest")
902 def _check((rc, out, err)):
903 self.failUnlessReallyEqual(rc, 1)
904 self.failUnlessIn("error:", err)
905 self.failUnlessReallyEqual(out, "")
906 d.addCallback(_check)
909 def test_manifest_with_nonexistent_alias(self):
910 # 'tahoe manifest' should output a useful error message when invoked
911 # with an explicit alias that doesn't exist.
912 self.basedir = "cli/Manifest/manifest_with_nonexistent_alias"
914 d = self.do_cli("manifest", "nonexistent:")
915 def _check((rc, out, err)):
916 self.failUnlessReallyEqual(rc, 1)
917 self.failUnlessIn("error:", err)
918 self.failUnlessIn("nonexistent", err)
919 self.failUnlessReallyEqual(out, "")
920 d.addCallback(_check)
924 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase):
925 def test_mkdir(self):
926 self.basedir = os.path.dirname(self.mktemp())
929 d = self.do_cli("create-alias", "tahoe")
930 d.addCallback(lambda res: self.do_cli("mkdir", "test"))
931 def _check((rc, out, err)):
932 self.failUnlessReallyEqual(rc, 0)
933 self.failUnlessReallyEqual(err, "")
934 self.failUnlessIn("URI:", out)
935 d.addCallback(_check)
939 def test_mkdir_mutable_type(self):
940 self.basedir = os.path.dirname(self.mktemp())
942 d = self.do_cli("create-alias", "tahoe")
943 def _check((rc, out, err), st):
944 self.failUnlessReallyEqual(rc, 0)
945 self.failUnlessReallyEqual(err, "")
946 self.failUnlessIn(st, out)
948 def _mkdir(ign, mutable_type, uri_prefix, dirname):
949 d2 = self.do_cli("mkdir", "--format="+mutable_type, dirname)
950 d2.addCallback(_check, uri_prefix)
951 def _stash_filecap(cap):
952 u = uri.from_string(cap)
953 fn_uri = u.get_filenode_cap()
954 self._filecap = fn_uri.to_string()
955 d2.addCallback(_stash_filecap)
956 d2.addCallback(lambda ign: self.do_cli("ls", "--json", dirname))
957 d2.addCallback(_check, uri_prefix)
958 d2.addCallback(lambda ign: self.do_cli("ls", "--json", self._filecap))
959 d2.addCallback(_check, '"format": "%s"' % (mutable_type.upper(),))
962 d.addCallback(_mkdir, "sdmf", "URI:DIR2", "tahoe:foo")
963 d.addCallback(_mkdir, "SDMF", "URI:DIR2", "tahoe:foo2")
964 d.addCallback(_mkdir, "mdmf", "URI:DIR2-MDMF", "tahoe:bar")
965 d.addCallback(_mkdir, "MDMF", "URI:DIR2-MDMF", "tahoe:bar2")
968 def test_mkdir_mutable_type_unlinked(self):
969 self.basedir = os.path.dirname(self.mktemp())
971 d = self.do_cli("mkdir", "--format=SDMF")
972 def _check((rc, out, err), st):
973 self.failUnlessReallyEqual(rc, 0)
974 self.failUnlessReallyEqual(err, "")
975 self.failUnlessIn(st, out)
977 d.addCallback(_check, "URI:DIR2")
978 def _stash_dircap(cap):
980 # Now we're going to feed the cap into uri.from_string...
981 u = uri.from_string(cap)
982 # ...grab the underlying filenode uri.
983 fn_uri = u.get_filenode_cap()
985 self._filecap = fn_uri.to_string()
986 d.addCallback(_stash_dircap)
987 d.addCallback(lambda res: self.do_cli("ls", "--json",
989 d.addCallback(_check, '"format": "SDMF"')
990 d.addCallback(lambda res: self.do_cli("mkdir", "--format=MDMF"))
991 d.addCallback(_check, "URI:DIR2-MDMF")
992 d.addCallback(_stash_dircap)
993 d.addCallback(lambda res: self.do_cli("ls", "--json",
995 d.addCallback(_check, '"format": "MDMF"')
998 def test_mkdir_bad_mutable_type(self):
999 o = cli.MakeDirectoryOptions()
1000 self.failUnlessRaises(usage.UsageError,
1004 def test_mkdir_unicode(self):
1005 self.basedir = os.path.dirname(self.mktemp())
1009 motorhead_arg = u"tahoe:Mot\u00F6rhead".encode(get_io_encoding())
1010 except UnicodeEncodeError:
1011 raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
1013 d = self.do_cli("create-alias", "tahoe")
1014 d.addCallback(lambda res: self.do_cli("mkdir", motorhead_arg))
1015 def _check((rc, out, err)):
1016 self.failUnlessReallyEqual(rc, 0)
1017 self.failUnlessReallyEqual(err, "")
1018 self.failUnlessIn("URI:", out)
1019 d.addCallback(_check)
1023 def test_mkdir_with_nonexistent_alias(self):
1024 # when invoked with an alias that doesn't exist, 'tahoe mkdir' should
1025 # output a sensible error message rather than a stack trace.
1026 self.basedir = "cli/Mkdir/mkdir_with_nonexistent_alias"
1028 d = self.do_cli("mkdir", "havasu:")
1029 def _check((rc, out, err)):
1030 self.failUnlessReallyEqual(rc, 1)
1031 self.failUnlessIn("error:", err)
1032 self.failUnlessReallyEqual(out, "")
1033 d.addCallback(_check)
1037 class Unlink(GridTestMixin, CLITestMixin, unittest.TestCase):
1040 def _create_test_file(self):
1041 data = "puppies" * 1000
1042 path = os.path.join(self.basedir, "datafile")
1043 fileutil.write(path, data)
1044 self.datafile = path
1046 def test_unlink_without_alias(self):
1047 # 'tahoe unlink' should behave sensibly when invoked without an explicit
1048 # alias before the default 'tahoe' alias has been created.
1049 self.basedir = "cli/Unlink/%s_without_alias" % (self.command,)
1051 d = self.do_cli(self.command, "afile")
1052 def _check((rc, out, err)):
1053 self.failUnlessReallyEqual(rc, 1)
1054 self.failUnlessIn("error:", err)
1055 self.failUnlessReallyEqual(out, "")
1056 d.addCallback(_check)
1058 d.addCallback(lambda ign: self.do_cli(self.command, "afile"))
1059 d.addCallback(_check)
1062 def test_unlink_with_nonexistent_alias(self):
1063 # 'tahoe unlink' should behave sensibly when invoked with an explicit
1064 # alias that doesn't exist.
1065 self.basedir = "cli/Unlink/%s_with_nonexistent_alias" % (self.command,)
1067 d = self.do_cli(self.command, "nonexistent:afile")
1068 def _check((rc, out, err)):
1069 self.failUnlessReallyEqual(rc, 1)
1070 self.failUnlessIn("error:", err)
1071 self.failUnlessIn("nonexistent", err)
1072 self.failUnlessReallyEqual(out, "")
1073 d.addCallback(_check)
1075 d.addCallback(lambda ign: self.do_cli(self.command, "nonexistent:afile"))
1076 d.addCallback(_check)
1079 def test_unlink_without_path(self):
1080 # 'tahoe unlink' should give a sensible error message when invoked without a path.
1081 self.basedir = "cli/Unlink/%s_without_path" % (self.command,)
1083 self._create_test_file()
1084 d = self.do_cli("create-alias", "tahoe")
1085 d.addCallback(lambda ign: self.do_cli("put", self.datafile, "tahoe:test"))
1086 def _do_unlink((rc, out, err)):
1087 self.failUnlessReallyEqual(rc, 0)
1088 self.failUnless(out.startswith("URI:"), out)
1089 return self.do_cli(self.command, out.strip('\n'))
1090 d.addCallback(_do_unlink)
1092 def _check((rc, out, err)):
1093 self.failUnlessReallyEqual(rc, 1)
1094 self.failUnlessIn("'tahoe %s'" % (self.command,), err)
1095 self.failUnlessIn("path must be given", err)
1096 self.failUnlessReallyEqual(out, "")
1097 d.addCallback(_check)
1102 """Test that 'tahoe rm' behaves in the same way as 'tahoe unlink'."""
1106 class Stats(GridTestMixin, CLITestMixin, unittest.TestCase):
1107 def test_empty_directory(self):
1108 self.basedir = "cli/Stats/empty_directory"
1110 c0 = self.g.clients[0]
1112 d = c0.create_dirnode()
1115 self.rooturi = n.get_uri()
1116 d.addCallback(_stash_root)
1118 # make sure we can get stats on an empty directory too
1119 d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
1120 def _check_stats((rc, out, err)):
1121 self.failUnlessReallyEqual(err, "")
1122 self.failUnlessReallyEqual(rc, 0)
1123 lines = out.splitlines()
1124 self.failUnlessIn(" count-immutable-files: 0", lines)
1125 self.failUnlessIn(" count-mutable-files: 0", lines)
1126 self.failUnlessIn(" count-literal-files: 0", lines)
1127 self.failUnlessIn(" count-directories: 1", lines)
1128 self.failUnlessIn(" size-immutable-files: 0", lines)
1129 self.failIfIn("Size Histogram:", lines)
1130 d.addCallback(_check_stats)
1134 def test_stats_without_alias(self):
1135 # when invoked with no explicit alias and before the default 'tahoe'
1136 # alias is created, 'tahoe stats' should output an informative error
1137 # message, not a stack trace.
1138 self.basedir = "cli/Stats/stats_without_alias"
1140 d = self.do_cli("stats")
1141 def _check((rc, out, err)):
1142 self.failUnlessReallyEqual(rc, 1)
1143 self.failUnlessIn("error:", err)
1144 self.failUnlessReallyEqual(out, "")
1145 d.addCallback(_check)
1148 def test_stats_with_nonexistent_alias(self):
1149 # when invoked with an explicit alias that doesn't exist,
1150 # 'tahoe stats' should output a useful error message.
1151 self.basedir = "cli/Stats/stats_with_nonexistent_alias"
1153 d = self.do_cli("stats", "havasu:")
1154 def _check((rc, out, err)):
1155 self.failUnlessReallyEqual(rc, 1)
1156 self.failUnlessIn("error:", err)
1157 self.failUnlessReallyEqual(out, "")
1158 d.addCallback(_check)
1162 class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase):
1163 def test_webopen_with_nonexistent_alias(self):
1164 # when invoked with an alias that doesn't exist, 'tahoe webopen'
1165 # should output an informative error message instead of a stack
1167 self.basedir = "cli/Webopen/webopen_with_nonexistent_alias"
1169 d = self.do_cli("webopen", "fake:")
1170 def _check((rc, out, err)):
1171 self.failUnlessReallyEqual(rc, 1)
1172 self.failUnlessIn("error:", err)
1173 self.failUnlessReallyEqual(out, "")
1174 d.addCallback(_check)
1177 def test_webopen(self):
1178 # TODO: replace with @patch that supports Deferreds.
1180 def call_webbrowser_open(url):
1181 self.failUnlessIn(self.alias_uri.replace(':', '%3A'), url)
1182 self.webbrowser_open_called = True
1184 webbrowser.open = self.old_webbrowser_open
1187 self.old_webbrowser_open = webbrowser.open
1189 webbrowser.open = call_webbrowser_open
1191 self.basedir = "cli/Webopen/webopen"
1193 d = self.do_cli("create-alias", "alias:")
1194 def _check_alias((rc, out, err)):
1195 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1196 self.failUnlessIn("Alias 'alias' created", out)
1197 self.failUnlessReallyEqual(err, "")
1198 self.alias_uri = get_aliases(self.get_clientdir())["alias"]
1199 d.addCallback(_check_alias)
1200 d.addCallback(lambda res: self.do_cli("webopen", "alias:"))
1201 def _check_webopen((rc, out, err)):
1202 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1203 self.failUnlessReallyEqual(out, "")
1204 self.failUnlessReallyEqual(err, "")
1205 self.failUnless(self.webbrowser_open_called)
1206 d.addCallback(_check_webopen)
1213 class Options(ReallyEqualMixin, unittest.TestCase):
1214 # this test case only looks at argument-processing and simple stuff.
1216 def parse(self, args, stdout=None):
1217 o = runner.Options()
1218 if stdout is not None:
1220 o.parseOptions(args)
1221 while hasattr(o, "subOptions"):
1225 def test_list(self):
1226 fileutil.rm_dir("cli/test_options")
1227 fileutil.make_dirs("cli/test_options")
1228 fileutil.make_dirs("cli/test_options/private")
1229 fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n")
1230 filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16,
1231 fingerprint="\x00"*32)
1232 private_uri = uri.DirectoryURI(filenode_uri).to_string()
1233 fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n")
1234 def parse2(args): return parse_options("cli/test_options", "ls", args)
1236 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1237 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1238 self.failUnlessEqual(o.where, u"")
1240 o = parse2(["--node-url", "http://example.org:8111/"])
1241 self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
1242 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1243 self.failUnlessEqual(o.where, u"")
1245 o = parse2(["--dir-cap", "root"])
1246 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1247 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
1248 self.failUnlessEqual(o.where, u"")
1250 other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
1251 fingerprint="\x11"*32)
1252 other_uri = uri.DirectoryURI(other_filenode_uri).to_string()
1253 o = parse2(["--dir-cap", other_uri])
1254 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1255 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1256 self.failUnlessEqual(o.where, u"")
1258 o = parse2(["--dir-cap", other_uri, "subdir"])
1259 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1260 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1261 self.failUnlessEqual(o.where, u"subdir")
1263 self.failUnlessRaises(usage.UsageError, parse2,
1264 ["--node-url", "NOT-A-URL"])
1266 o = parse2(["--node-url", "http://localhost:8080"])
1267 self.failUnlessEqual(o["node-url"], "http://localhost:8080/")
1269 o = parse2(["--node-url", "https://localhost/"])
1270 self.failUnlessEqual(o["node-url"], "https://localhost/")
1272 def test_version(self):
1273 # "tahoe --version" dumps text to stdout and exits
1275 self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout)
1276 self.failUnlessIn(allmydata.__appname__ + ":", stdout.getvalue())
1277 # but "tahoe SUBCOMMAND --version" should be rejected
1278 self.failUnlessRaises(usage.UsageError, self.parse,
1279 ["start", "--version"])
1280 self.failUnlessRaises(usage.UsageError, self.parse,
1281 ["start", "--version-and-path"])
1283 def test_quiet(self):
1284 # accepted as an overall option, but not on subcommands
1285 o = self.parse(["--quiet", "start"])
1286 self.failUnless(o.parent["quiet"])
1287 self.failUnlessRaises(usage.UsageError, self.parse,
1288 ["start", "--quiet"])
1290 def test_basedir(self):
1291 # accept a --node-directory option before the verb, or a --basedir
1292 # option after, or a basedir argument after, but none in the wrong
1293 # place, and not more than one of the three.
1294 o = self.parse(["start"])
1295 self.failUnlessReallyEqual(o["basedir"], os.path.join(fileutil.abspath_expanduser_unicode(u"~"),
1297 o = self.parse(["start", "here"])
1298 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1299 o = self.parse(["start", "--basedir", "there"])
1300 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1301 o = self.parse(["--node-directory", "there", "start"])
1302 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1304 o = self.parse(["start", "here", "--nodaemon"])
1305 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1307 self.failUnlessRaises(usage.UsageError, self.parse,
1308 ["--basedir", "there", "start"])
1309 self.failUnlessRaises(usage.UsageError, self.parse,
1310 ["start", "--node-directory", "there"])
1312 self.failUnlessRaises(usage.UsageError, self.parse,
1313 ["--node-directory=there",
1314 "start", "--basedir=here"])
1315 self.failUnlessRaises(usage.UsageError, self.parse,
1316 ["start", "--basedir=here", "anywhere"])
1317 self.failUnlessRaises(usage.UsageError, self.parse,
1318 ["--node-directory=there",
1319 "start", "anywhere"])
1320 self.failUnlessRaises(usage.UsageError, self.parse,
1321 ["--node-directory=there",
1322 "start", "--basedir=here", "anywhere"])
1324 self.failUnlessRaises(usage.UsageError, self.parse,
1325 ["--node-directory=there", "start", "--nodaemon"])
1326 self.failUnlessRaises(usage.UsageError, self.parse,
1327 ["start", "--basedir=here", "--nodaemon"])