3 from cStringIO import StringIO
6 from twisted.trial import unittest
7 from twisted.python.monkey import MonkeyPatcher
10 from allmydata.util import fileutil, hashutil, base32, keyutil
11 from allmydata.util.namespace import Namespace
12 from allmydata import uri
13 from allmydata.immutable import upload
14 from allmydata.dirnode import normalize
15 from allmydata.scripts.common_http import socket_error
16 import allmydata.scripts.common_http
17 from pycryptopp.publickey import ed25519
19 # Test that the scripts can be imported.
20 from allmydata.scripts import create_node, debug, keygen, startstop_node, \
21 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls, \
22 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen
23 _hush_pyflakes = [create_node, debug, keygen, startstop_node,
24 tahoe_add_alias, tahoe_backup, tahoe_check, tahoe_cp, tahoe_get, tahoe_ls,
25 tahoe_manifest, tahoe_mkdir, tahoe_mv, tahoe_put, tahoe_unlink, tahoe_webopen]
27 from allmydata.scripts import common
28 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases, get_alias, \
31 from allmydata.scripts import cli, debug, runner
32 from allmydata.test.common_util import ReallyEqualMixin
33 from allmydata.test.no_network import GridTestMixin
34 from twisted.internet import threads # CLI tests use deferToThread
35 from twisted.python import usage
37 from allmydata.util.assertutil import precondition
38 from allmydata.util.encodingutil import listdir_unicode, unicode_platform, \
39 get_io_encoding, get_filesystem_encoding
41 timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s
43 def parse_options(basedir, command, args):
45 o.parseOptions(["--node-directory", basedir, command] + args)
46 while hasattr(o, "subOptions"):
50 class CLITestMixin(ReallyEqualMixin):
51 def do_cli(self, verb, *args, **kwargs):
53 "--node-directory", self.get_clientdir(),
55 argv = nodeargs + [verb] + list(args)
56 stdin = kwargs.get("stdin", "")
57 stdout, stderr = StringIO(), StringIO()
58 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
59 stdin=StringIO(stdin),
60 stdout=stdout, stderr=stderr)
62 return rc, stdout.getvalue(), stderr.getvalue()
66 def skip_if_cannot_represent_filename(self, u):
67 precondition(isinstance(u, unicode))
69 enc = get_filesystem_encoding()
70 if not unicode_platform():
73 except UnicodeEncodeError:
74 raise unittest.SkipTest("A non-ASCII filename could not be encoded on this platform.")
77 class CLI(CLITestMixin, unittest.TestCase):
78 def _dump_cap(self, *args):
79 config = debug.DumpCapOptions()
80 config.stdout,config.stderr = StringIO(), StringIO()
81 config.parseOptions(args)
82 debug.dump_cap(config)
83 self.failIf(config.stderr.getvalue())
84 output = config.stdout.getvalue()
87 def test_dump_cap_chk(self):
88 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
89 uri_extension_hash = hashutil.uri_extension_hash("stuff")
93 u = uri.CHKFileURI(key=key,
94 uri_extension_hash=uri_extension_hash,
95 needed_shares=needed_shares,
96 total_shares=total_shares,
98 output = self._dump_cap(u.to_string())
99 self.failUnless("CHK File:" in output, output)
100 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
101 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
102 self.failUnless("size: 1234" in output, output)
103 self.failUnless("k/N: 25/100" in output, output)
104 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
106 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
108 self.failUnless("client renewal secret: znxmki5zdibb5qlt46xbdvk2t55j7hibejq3i5ijyurkr6m6jkhq" in output, output)
110 output = self._dump_cap(u.get_verify_cap().to_string())
111 self.failIf("key: " in output, output)
112 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
113 self.failUnless("size: 1234" in output, output)
114 self.failUnless("k/N: 25/100" in output, output)
115 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
117 prefixed_u = "http://127.0.0.1/uri/%s" % urllib.quote(u.to_string())
118 output = self._dump_cap(prefixed_u)
119 self.failUnless("CHK File:" in output, output)
120 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
121 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
122 self.failUnless("size: 1234" in output, output)
123 self.failUnless("k/N: 25/100" in output, output)
124 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
126 def test_dump_cap_lit(self):
127 u = uri.LiteralFileURI("this is some data")
128 output = self._dump_cap(u.to_string())
129 self.failUnless("Literal File URI:" in output, output)
130 self.failUnless("data: 'this is some data'" in output, output)
132 def test_dump_cap_sdmf(self):
133 writekey = "\x01" * 16
134 fingerprint = "\xfe" * 32
135 u = uri.WriteableSSKFileURI(writekey, fingerprint)
137 output = self._dump_cap(u.to_string())
138 self.failUnless("SDMF Writeable URI:" in output, output)
139 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
140 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
141 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
142 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
144 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
146 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
148 fileutil.make_dirs("cli/test_dump_cap/private")
149 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
150 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
152 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
154 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
156 self.failIf("file renewal secret:" in output, output)
158 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
160 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
161 self.failIf("file renewal secret:" in output, output)
163 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
164 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
166 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
167 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
168 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
171 output = self._dump_cap(u.to_string())
172 self.failUnless("SDMF Read-only URI:" in output, output)
173 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
174 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
175 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
177 u = u.get_verify_cap()
178 output = self._dump_cap(u.to_string())
179 self.failUnless("SDMF Verifier URI:" in output, output)
180 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
181 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
183 def test_dump_cap_mdmf(self):
184 writekey = "\x01" * 16
185 fingerprint = "\xfe" * 32
186 u = uri.WriteableMDMFFileURI(writekey, fingerprint)
188 output = self._dump_cap(u.to_string())
189 self.failUnless("MDMF Writeable URI:" in output, output)
190 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
191 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
192 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
193 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
195 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
197 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
199 fileutil.make_dirs("cli/test_dump_cap/private")
200 fileutil.write("cli/test_dump_cap/private/secret", "5s33nk3qpvnj2fw3z4mnm2y6fa\n")
201 output = self._dump_cap("--client-dir", "cli/test_dump_cap",
203 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
205 output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
207 self.failIf("file renewal secret:" in output, output)
209 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
211 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
212 self.failIf("file renewal secret:" in output, output)
214 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
215 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
217 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
218 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
219 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
222 output = self._dump_cap(u.to_string())
223 self.failUnless("MDMF Read-only URI:" in output, output)
224 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
225 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
226 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
228 u = u.get_verify_cap()
229 output = self._dump_cap(u.to_string())
230 self.failUnless("MDMF Verifier URI:" in output, output)
231 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
232 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
235 def test_dump_cap_chk_directory(self):
236 key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
237 uri_extension_hash = hashutil.uri_extension_hash("stuff")
241 u1 = uri.CHKFileURI(key=key,
242 uri_extension_hash=uri_extension_hash,
243 needed_shares=needed_shares,
244 total_shares=total_shares,
246 u = uri.ImmutableDirectoryURI(u1)
248 output = self._dump_cap(u.to_string())
249 self.failUnless("CHK Directory URI:" in output, output)
250 self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
251 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
252 self.failUnless("size: 1234" in output, output)
253 self.failUnless("k/N: 25/100" in output, output)
254 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
256 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
258 self.failUnless("file renewal secret: csrvkjgomkyyyil5yo4yk5np37p6oa2ve2hg6xmk2dy7kaxsu6xq" in output, output)
260 u = u.get_verify_cap()
261 output = self._dump_cap(u.to_string())
262 self.failUnless("CHK Directory Verifier URI:" in output, output)
263 self.failIf("key: " in output, output)
264 self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
265 self.failUnless("size: 1234" in output, output)
266 self.failUnless("k/N: 25/100" in output, output)
267 self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
269 def test_dump_cap_sdmf_directory(self):
270 writekey = "\x01" * 16
271 fingerprint = "\xfe" * 32
272 u1 = uri.WriteableSSKFileURI(writekey, fingerprint)
273 u = uri.DirectoryURI(u1)
275 output = self._dump_cap(u.to_string())
276 self.failUnless("Directory Writeable URI:" in output, output)
277 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
279 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
280 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
282 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
284 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
286 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
288 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
290 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
291 self.failIf("file renewal secret:" in output, output)
293 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
294 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
296 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
297 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
298 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
301 output = self._dump_cap(u.to_string())
302 self.failUnless("Directory Read-only URI:" in output, output)
303 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
304 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
305 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
307 u = u.get_verify_cap()
308 output = self._dump_cap(u.to_string())
309 self.failUnless("Directory Verifier URI:" in output, output)
310 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
311 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
313 def test_dump_cap_mdmf_directory(self):
314 writekey = "\x01" * 16
315 fingerprint = "\xfe" * 32
316 u1 = uri.WriteableMDMFFileURI(writekey, fingerprint)
317 u = uri.MDMFDirectoryURI(u1)
319 output = self._dump_cap(u.to_string())
320 self.failUnless("Directory Writeable URI:" in output, output)
321 self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
323 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
324 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
326 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
328 output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
330 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
332 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
334 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
335 self.failIf("file renewal secret:" in output, output)
337 output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
338 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
340 self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
341 self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
342 self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
345 output = self._dump_cap(u.to_string())
346 self.failUnless("Directory Read-only URI:" in output, output)
347 self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
348 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
349 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
351 u = u.get_verify_cap()
352 output = self._dump_cap(u.to_string())
353 self.failUnless("Directory Verifier URI:" in output, output)
354 self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
355 self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
358 def _catalog_shares(self, *basedirs):
359 o = debug.CatalogSharesOptions()
360 o.stdout,o.stderr = StringIO(), StringIO()
361 args = list(basedirs)
363 debug.catalog_shares(o)
364 out = o.stdout.getvalue()
365 err = o.stderr.getvalue()
368 def test_catalog_shares_error(self):
369 nodedir1 = "cli/test_catalog_shares/node1"
370 sharedir = os.path.join(nodedir1, "storage", "shares", "mq", "mqfblse6m5a6dh45isu2cg7oji")
371 fileutil.make_dirs(sharedir)
372 fileutil.write("cli/test_catalog_shares/node1/storage/shares/mq/not-a-dir", "")
373 # write a bogus share that looks a little bit like CHK
374 fileutil.write(os.path.join(sharedir, "8"),
375 "\x00\x00\x00\x01" + "\xff" * 200) # this triggers an assert
377 nodedir2 = "cli/test_catalog_shares/node2"
378 fileutil.make_dirs(nodedir2)
379 fileutil.write("cli/test_catalog_shares/node1/storage/shares/not-a-dir", "")
381 # now make sure that the 'catalog-shares' commands survives the error
382 out, err = self._catalog_shares(nodedir1, nodedir2)
383 self.failUnlessReallyEqual(out, "", out)
384 self.failUnless("Error processing " in err,
385 "didn't see 'error processing' in '%s'" % err)
386 #self.failUnless(nodedir1 in err,
387 # "didn't see '%s' in '%s'" % (nodedir1, err))
388 # windows mangles the path, and os.path.join isn't enough to make
389 # up for it, so just look for individual strings
390 self.failUnless("node1" in err,
391 "didn't see 'node1' in '%s'" % err)
392 self.failUnless("mqfblse6m5a6dh45isu2cg7oji" in err,
393 "didn't see 'mqfblse6m5a6dh45isu2cg7oji' in '%s'" % err)
395 def test_alias(self):
396 def s128(c): return base32.b2a(c*(128/8))
397 def s256(c): return base32.b2a(c*(256/8))
398 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
399 WA = "URI:DIR2:%s:%s" % (s128("W"), s256("W"))
400 CA = "URI:DIR2:%s:%s" % (s128("C"), s256("C"))
401 aliases = {"tahoe": TA,
405 return get_alias(aliases, path, u"tahoe")
406 uses_lettercolon = common.platform_uses_lettercolon_drivename()
407 self.failUnlessReallyEqual(ga1(u"bare"), (TA, "bare"))
408 self.failUnlessReallyEqual(ga1(u"baredir/file"), (TA, "baredir/file"))
409 self.failUnlessReallyEqual(ga1(u"baredir/file:7"), (TA, "baredir/file:7"))
410 self.failUnlessReallyEqual(ga1(u"tahoe:"), (TA, ""))
411 self.failUnlessReallyEqual(ga1(u"tahoe:file"), (TA, "file"))
412 self.failUnlessReallyEqual(ga1(u"tahoe:dir/file"), (TA, "dir/file"))
413 self.failUnlessReallyEqual(ga1(u"work:"), (WA, ""))
414 self.failUnlessReallyEqual(ga1(u"work:file"), (WA, "file"))
415 self.failUnlessReallyEqual(ga1(u"work:dir/file"), (WA, "dir/file"))
416 # default != None means we really expect a tahoe path, regardless of
417 # whether we're on windows or not. This is what 'tahoe get' uses.
418 self.failUnlessReallyEqual(ga1(u"c:"), (CA, ""))
419 self.failUnlessReallyEqual(ga1(u"c:file"), (CA, "file"))
420 self.failUnlessReallyEqual(ga1(u"c:dir/file"), (CA, "dir/file"))
421 self.failUnlessReallyEqual(ga1(u"URI:stuff"), ("URI:stuff", ""))
422 self.failUnlessReallyEqual(ga1(u"URI:stuff/file"), ("URI:stuff", "file"))
423 self.failUnlessReallyEqual(ga1(u"URI:stuff:./file"), ("URI:stuff", "file"))
424 self.failUnlessReallyEqual(ga1(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
425 self.failUnlessReallyEqual(ga1(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
426 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:")
427 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir")
428 self.failUnlessRaises(common.UnknownAliasError, ga1, u"missing:dir/file")
431 return get_alias(aliases, path, None)
432 self.failUnlessReallyEqual(ga2(u"bare"), (DefaultAliasMarker, "bare"))
433 self.failUnlessReallyEqual(ga2(u"baredir/file"),
434 (DefaultAliasMarker, "baredir/file"))
435 self.failUnlessReallyEqual(ga2(u"baredir/file:7"),
436 (DefaultAliasMarker, "baredir/file:7"))
437 self.failUnlessReallyEqual(ga2(u"baredir/sub:1/file:7"),
438 (DefaultAliasMarker, "baredir/sub:1/file:7"))
439 self.failUnlessReallyEqual(ga2(u"tahoe:"), (TA, ""))
440 self.failUnlessReallyEqual(ga2(u"tahoe:file"), (TA, "file"))
441 self.failUnlessReallyEqual(ga2(u"tahoe:dir/file"), (TA, "dir/file"))
442 # on windows, we really want c:foo to indicate a local file.
443 # default==None is what 'tahoe cp' uses.
445 self.failUnlessReallyEqual(ga2(u"c:"), (DefaultAliasMarker, "c:"))
446 self.failUnlessReallyEqual(ga2(u"c:file"), (DefaultAliasMarker, "c:file"))
447 self.failUnlessReallyEqual(ga2(u"c:dir/file"),
448 (DefaultAliasMarker, "c:dir/file"))
450 self.failUnlessReallyEqual(ga2(u"c:"), (CA, ""))
451 self.failUnlessReallyEqual(ga2(u"c:file"), (CA, "file"))
452 self.failUnlessReallyEqual(ga2(u"c:dir/file"), (CA, "dir/file"))
453 self.failUnlessReallyEqual(ga2(u"work:"), (WA, ""))
454 self.failUnlessReallyEqual(ga2(u"work:file"), (WA, "file"))
455 self.failUnlessReallyEqual(ga2(u"work:dir/file"), (WA, "dir/file"))
456 self.failUnlessReallyEqual(ga2(u"URI:stuff"), ("URI:stuff", ""))
457 self.failUnlessReallyEqual(ga2(u"URI:stuff/file"), ("URI:stuff", "file"))
458 self.failUnlessReallyEqual(ga2(u"URI:stuff:./file"), ("URI:stuff", "file"))
459 self.failUnlessReallyEqual(ga2(u"URI:stuff/dir/file"), ("URI:stuff", "dir/file"))
460 self.failUnlessReallyEqual(ga2(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
461 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:")
462 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir")
463 self.failUnlessRaises(common.UnknownAliasError, ga2, u"missing:dir/file")
466 old = common.pretend_platform_uses_lettercolon
468 common.pretend_platform_uses_lettercolon = True
469 retval = get_alias(aliases, path, None)
471 common.pretend_platform_uses_lettercolon = old
473 self.failUnlessReallyEqual(ga3(u"bare"), (DefaultAliasMarker, "bare"))
474 self.failUnlessReallyEqual(ga3(u"baredir/file"),
475 (DefaultAliasMarker, "baredir/file"))
476 self.failUnlessReallyEqual(ga3(u"baredir/file:7"),
477 (DefaultAliasMarker, "baredir/file:7"))
478 self.failUnlessReallyEqual(ga3(u"baredir/sub:1/file:7"),
479 (DefaultAliasMarker, "baredir/sub:1/file:7"))
480 self.failUnlessReallyEqual(ga3(u"tahoe:"), (TA, ""))
481 self.failUnlessReallyEqual(ga3(u"tahoe:file"), (TA, "file"))
482 self.failUnlessReallyEqual(ga3(u"tahoe:dir/file"), (TA, "dir/file"))
483 self.failUnlessReallyEqual(ga3(u"c:"), (DefaultAliasMarker, "c:"))
484 self.failUnlessReallyEqual(ga3(u"c:file"), (DefaultAliasMarker, "c:file"))
485 self.failUnlessReallyEqual(ga3(u"c:dir/file"),
486 (DefaultAliasMarker, "c:dir/file"))
487 self.failUnlessReallyEqual(ga3(u"work:"), (WA, ""))
488 self.failUnlessReallyEqual(ga3(u"work:file"), (WA, "file"))
489 self.failUnlessReallyEqual(ga3(u"work:dir/file"), (WA, "dir/file"))
490 self.failUnlessReallyEqual(ga3(u"URI:stuff"), ("URI:stuff", ""))
491 self.failUnlessReallyEqual(ga3(u"URI:stuff:./file"), ("URI:stuff", "file"))
492 self.failUnlessReallyEqual(ga3(u"URI:stuff:./dir/file"), ("URI:stuff", "dir/file"))
493 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:")
494 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir")
495 self.failUnlessRaises(common.UnknownAliasError, ga3, u"missing:dir/file")
496 # calling get_alias with a path that doesn't include an alias and
497 # default set to something that isn't in the aliases argument should
498 # raise an UnknownAliasError.
500 return get_alias(aliases, path, u"badddefault:")
501 self.failUnlessRaises(common.UnknownAliasError, ga4, u"afile")
502 self.failUnlessRaises(common.UnknownAliasError, ga4, u"a/dir/path/")
505 old = common.pretend_platform_uses_lettercolon
507 common.pretend_platform_uses_lettercolon = True
508 retval = get_alias(aliases, path, u"baddefault:")
510 common.pretend_platform_uses_lettercolon = old
512 self.failUnlessRaises(common.UnknownAliasError, ga5, u"C:\\Windows")
514 def test_alias_tolerance(self):
515 def s128(c): return base32.b2a(c*(128/8))
516 def s256(c): return base32.b2a(c*(256/8))
517 TA = "URI:DIR2:%s:%s" % (s128("T"), s256("T"))
518 aliases = {"present": TA,
519 "future": "URI-FROM-FUTURE:ooh:aah"}
521 return get_alias(aliases, path, u"tahoe")
522 self.failUnlessReallyEqual(ga1(u"present:file"), (TA, "file"))
523 # this throws, via assert IDirnodeURI.providedBy(), since get_alias()
524 # wants a dirnode, and the future cap gives us UnknownURI instead.
525 self.failUnlessRaises(AssertionError, ga1, u"future:stuff")
527 def test_listdir_unicode_good(self):
528 filenames = [u'L\u00F4zane', u'Bern', u'Gen\u00E8ve'] # must be NFC
530 for name in filenames:
531 self.skip_if_cannot_represent_filename(name)
533 basedir = "cli/common/listdir_unicode_good"
534 fileutil.make_dirs(basedir)
536 for name in filenames:
537 open(os.path.join(unicode(basedir), name), "wb").close()
539 for file in listdir_unicode(unicode(basedir)):
540 self.failUnlessIn(normalize(file), filenames)
542 def test_exception_catcher(self):
543 self.basedir = "cli/exception_catcher"
546 exc = Exception("canary")
549 ns.runner_called = False
550 def call_runner(args, install_node_control=True):
551 ns.runner_called = True
552 self.failUnlessEqual(install_node_control, True)
555 ns.sys_exit_called = False
556 def call_sys_exit(exitcode):
557 ns.sys_exit_called = True
558 self.failUnlessEqual(exitcode, 1)
560 patcher = MonkeyPatcher((runner, 'runner', call_runner),
561 (sys, 'argv', ["tahoe"]),
562 (sys, 'exit', call_sys_exit),
563 (sys, 'stderr', stderr))
564 patcher.runWithPatches(runner.run)
566 self.failUnless(ns.runner_called)
567 self.failUnless(ns.sys_exit_called)
568 self.failUnlessIn(str(exc), stderr.getvalue())
571 class Help(unittest.TestCase):
572 def failUnlessInNormalized(self, x, y):
573 # helper function to deal with the --help output being wrapped to
574 # various widths, depending on the $COLUMNS environment variable
575 self.failUnlessIn(x.replace("\n", " "), y.replace("\n", " "))
578 help = str(cli.GetOptions())
579 self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help)
580 self.failUnlessIn("% tahoe get FOO |less", help)
583 help = str(cli.PutOptions())
584 self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help)
585 self.failUnlessIn("% cat FILE | tahoe put", help)
588 help = str(cli.ListOptions())
589 self.failUnlessIn("[options] [PATH]", help)
591 def test_unlink(self):
592 help = str(cli.UnlinkOptions())
593 self.failUnlessIn("[options] REMOTE_FILE", help)
596 help = str(cli.RmOptions())
597 self.failUnlessIn("[options] REMOTE_FILE", help)
600 help = str(cli.MvOptions())
601 self.failUnlessIn("[options] FROM TO", help)
602 self.failUnlessInNormalized("Use 'tahoe mv' to move files", help)
605 help = str(cli.CpOptions())
606 self.failUnlessIn("[options] FROM.. TO", help)
607 self.failUnlessInNormalized("Use 'tahoe cp' to copy files", help)
610 help = str(cli.LnOptions())
611 self.failUnlessIn("[options] FROM_LINK TO_LINK", help)
612 self.failUnlessInNormalized("Use 'tahoe ln' to duplicate a link", help)
614 def test_mkdir(self):
615 help = str(cli.MakeDirectoryOptions())
616 self.failUnlessIn("[options] [REMOTE_DIR]", help)
617 self.failUnlessInNormalized("Create a new directory", help)
619 def test_backup(self):
620 help = str(cli.BackupOptions())
621 self.failUnlessIn("[options] FROM ALIAS:TO", help)
623 def test_webopen(self):
624 help = str(cli.WebopenOptions())
625 self.failUnlessIn("[options] [ALIAS:PATH]", help)
627 def test_manifest(self):
628 help = str(cli.ManifestOptions())
629 self.failUnlessIn("[options] [ALIAS:PATH]", help)
631 def test_stats(self):
632 help = str(cli.StatsOptions())
633 self.failUnlessIn("[options] [ALIAS:PATH]", help)
635 def test_check(self):
636 help = str(cli.CheckOptions())
637 self.failUnlessIn("[options] [ALIAS:PATH]", help)
639 def test_deep_check(self):
640 help = str(cli.DeepCheckOptions())
641 self.failUnlessIn("[options] [ALIAS:PATH]", help)
643 def test_create_alias(self):
644 help = str(cli.CreateAliasOptions())
645 self.failUnlessIn("[options] ALIAS[:]", help)
647 def test_add_alias(self):
648 help = str(cli.AddAliasOptions())
649 self.failUnlessIn("[options] ALIAS[:] DIRCAP", help)
651 def test_list_aliases(self):
652 help = str(cli.ListAliasesOptions())
653 self.failUnlessIn("[options]", help)
655 def test_start(self):
656 help = str(startstop_node.StartOptions())
657 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
660 help = str(startstop_node.StopOptions())
661 self.failUnlessIn("[options] [NODEDIR]", help)
663 def test_restart(self):
664 help = str(startstop_node.RestartOptions())
665 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
668 help = str(startstop_node.RunOptions())
669 self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
671 def test_create_client(self):
672 help = str(create_node.CreateClientOptions())
673 self.failUnlessIn("[options] [NODEDIR]", help)
675 def test_create_node(self):
676 help = str(create_node.CreateNodeOptions())
677 self.failUnlessIn("[options] [NODEDIR]", help)
679 def test_create_introducer(self):
680 help = str(create_node.CreateIntroducerOptions())
681 self.failUnlessIn("[options] NODEDIR", help)
683 def test_debug_trial(self):
684 help = str(debug.TrialOptions())
685 self.failUnlessIn(" [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help)
686 self.failUnlessInNormalized("The 'tahoe debug trial' command uses the correct imports", help)
688 def test_debug_flogtool(self):
689 options = debug.FlogtoolOptions()
691 self.failUnlessIn(" [global-options] debug flogtool ", help)
692 self.failUnlessInNormalized("The 'tahoe debug flogtool' command uses the correct imports", help)
694 for (option, shortcut, oClass, desc) in options.subCommands:
695 subhelp = str(oClass())
696 self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp)
699 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):
700 def _create_test_file(self):
701 data = "puppies" * 1000
702 path = os.path.join(self.basedir, "datafile")
703 fileutil.write(path, data)
706 def test_ln_without_alias(self):
707 # if invoked without an alias when the 'tahoe' alias doesn't
708 # exist, 'tahoe ln' should output a useful error message and not
710 self.basedir = "cli/Ln/ln_without_alias"
712 d = self.do_cli("ln", "from", "to")
713 def _check((rc, out, err)):
714 self.failUnlessReallyEqual(rc, 1)
715 self.failUnlessIn("error:", err)
716 self.failUnlessReallyEqual(out, "")
717 d.addCallback(_check)
718 # Make sure that validation extends to the "to" parameter
719 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
720 d.addCallback(lambda ign: self._create_test_file())
721 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
723 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "to"))
724 d.addCallback(_check)
727 def test_ln_with_nonexistent_alias(self):
728 # If invoked with aliases that don't exist, 'tahoe ln' should
729 # output a useful error message and not a stack trace.
730 self.basedir = "cli/Ln/ln_with_nonexistent_alias"
732 d = self.do_cli("ln", "havasu:from", "havasu:to")
733 def _check((rc, out, err)):
734 self.failUnlessReallyEqual(rc, 1)
735 self.failUnlessIn("error:", err)
736 d.addCallback(_check)
737 # Make sure that validation occurs on the to parameter if the
738 # from parameter passes.
739 d.addCallback(lambda ign: self.do_cli("create-alias", "havasu"))
740 d.addCallback(lambda ign: self._create_test_file())
741 d.addCallback(lambda ign: self.do_cli("put", self.datafile,
743 d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "huron:to"))
744 d.addCallback(_check)
748 class Admin(unittest.TestCase):
749 def do_cli(self, *args, **kwargs):
751 stdin = kwargs.get("stdin", "")
752 stdout, stderr = StringIO(), StringIO()
753 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
754 stdin=StringIO(stdin),
755 stdout=stdout, stderr=stderr)
757 return stdout.getvalue(), stderr.getvalue()
761 def test_generate_keypair(self):
762 d = self.do_cli("admin", "generate-keypair")
763 def _done( (stdout, stderr) ):
764 lines = [line.strip() for line in stdout.splitlines()]
765 privkey_bits = lines[0].split()
766 pubkey_bits = lines[1].split()
767 sk_header = "private:"
768 vk_header = "public:"
769 self.failUnlessEqual(privkey_bits[0], sk_header, lines[0])
770 self.failUnlessEqual(pubkey_bits[0], vk_header, lines[1])
771 self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0])
772 self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1])
773 sk_bytes = base32.a2b(keyutil.remove_prefix(privkey_bits[1], "priv-v0-"))
774 sk = ed25519.SigningKey(sk_bytes)
775 vk_bytes = base32.a2b(keyutil.remove_prefix(pubkey_bits[1], "pub-v0-"))
776 self.failUnlessEqual(sk.get_verifying_key_bytes(), vk_bytes)
780 def test_derive_pubkey(self):
781 priv1,pub1 = keyutil.make_keypair()
782 d = self.do_cli("admin", "derive-pubkey", priv1)
783 def _done( (stdout, stderr) ):
784 lines = stdout.split("\n")
785 privkey_line = lines[0].strip()
786 pubkey_line = lines[1].strip()
787 sk_header = "private: priv-v0-"
788 vk_header = "public: pub-v0-"
789 self.failUnless(privkey_line.startswith(sk_header), privkey_line)
790 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line)
791 pub2 = pubkey_line[len(vk_header):]
792 self.failUnlessEqual("pub-v0-"+pub2, pub1)
797 class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
799 self.basedir = "cli/Errors/get"
801 c0 = self.g.clients[0]
804 d = c0.upload(upload.Data(DATA, convergence=""))
806 self.uri_1share = ur.get_uri()
807 self.delete_shares_numbered(ur.get_uri(), range(1,10))
808 d.addCallback(_stash_bad)
810 # the download is abandoned as soon as it's clear that we won't get
811 # enough shares. The one remaining share might be in either the
812 # COMPLETE or the PENDING state.
813 in_complete_msg = "ran out of shares: complete=sh0 pending= overdue= unused= need 3"
814 in_pending_msg = "ran out of shares: complete= pending=Share(sh0-on-fob7vqgd) overdue= unused= need 3"
816 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share))
817 def _check1((rc, out, err)):
818 self.failIfEqual(rc, 0)
819 self.failUnless("410 Gone" in err, err)
820 self.failUnlessIn("NotEnoughSharesError: ", err)
821 self.failUnless(in_complete_msg in err or in_pending_msg in err,
823 d.addCallback(_check1)
825 targetf = os.path.join(self.basedir, "output")
826 d.addCallback(lambda ign: self.do_cli("get", self.uri_1share, targetf))
827 def _check2((rc, out, err)):
828 self.failIfEqual(rc, 0)
829 self.failUnless("410 Gone" in err, err)
830 self.failUnlessIn("NotEnoughSharesError: ", err)
831 self.failUnless(in_complete_msg in err or in_pending_msg in err,
833 self.failIf(os.path.exists(targetf))
834 d.addCallback(_check2)
838 def test_broken_socket(self):
839 # When the http connection breaks (such as when node.url is overwritten
840 # by a confused user), a user friendly error message should be printed.
841 self.basedir = "cli/Errors/test_broken_socket"
844 # Simulate a connection error
845 def _socket_error(*args, **kwargs):
846 raise socket_error('test error')
847 self.patch(allmydata.scripts.common_http.httplib.HTTPConnection,
848 "endheaders", _socket_error)
850 d = self.do_cli("mkdir")
851 def _check_invalid((rc,stdout,stderr)):
852 self.failIfEqual(rc, 0)
853 self.failUnlessIn("Error trying to connect to http://127.0.0.1", stderr)
854 d.addCallback(_check_invalid)
858 class Get(GridTestMixin, CLITestMixin, unittest.TestCase):
859 def test_get_without_alias(self):
860 # 'tahoe get' should output a useful error message when invoked
861 # without an explicit alias and when the default 'tahoe' alias
862 # hasn't been created yet.
863 self.basedir = "cli/Get/get_without_alias"
865 d = self.do_cli('get', 'file')
866 def _check((rc, out, err)):
867 self.failUnlessReallyEqual(rc, 1)
868 self.failUnlessIn("error:", err)
869 self.failUnlessReallyEqual(out, "")
870 d.addCallback(_check)
873 def test_get_with_nonexistent_alias(self):
874 # 'tahoe get' should output a useful error message when invoked with
875 # an explicit alias that doesn't exist.
876 self.basedir = "cli/Get/get_with_nonexistent_alias"
878 d = self.do_cli("get", "nonexistent:file")
879 def _check((rc, out, err)):
880 self.failUnlessReallyEqual(rc, 1)
881 self.failUnlessIn("error:", err)
882 self.failUnlessIn("nonexistent", err)
883 self.failUnlessReallyEqual(out, "")
884 d.addCallback(_check)
888 class Manifest(GridTestMixin, CLITestMixin, unittest.TestCase):
889 def test_manifest_without_alias(self):
890 # 'tahoe manifest' should output a useful error message when invoked
891 # without an explicit alias when the default 'tahoe' alias is
893 self.basedir = "cli/Manifest/manifest_without_alias"
895 d = self.do_cli("manifest")
896 def _check((rc, out, err)):
897 self.failUnlessReallyEqual(rc, 1)
898 self.failUnlessIn("error:", err)
899 self.failUnlessReallyEqual(out, "")
900 d.addCallback(_check)
903 def test_manifest_with_nonexistent_alias(self):
904 # 'tahoe manifest' should output a useful error message when invoked
905 # with an explicit alias that doesn't exist.
906 self.basedir = "cli/Manifest/manifest_with_nonexistent_alias"
908 d = self.do_cli("manifest", "nonexistent:")
909 def _check((rc, out, err)):
910 self.failUnlessReallyEqual(rc, 1)
911 self.failUnlessIn("error:", err)
912 self.failUnlessIn("nonexistent", err)
913 self.failUnlessReallyEqual(out, "")
914 d.addCallback(_check)
918 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase):
919 def test_mkdir(self):
920 self.basedir = os.path.dirname(self.mktemp())
923 d = self.do_cli("create-alias", "tahoe")
924 d.addCallback(lambda res: self.do_cli("mkdir", "test"))
925 def _check((rc, out, err)):
926 self.failUnlessReallyEqual(rc, 0)
927 self.failUnlessReallyEqual(err, "")
928 self.failUnlessIn("URI:", out)
929 d.addCallback(_check)
933 def test_mkdir_mutable_type(self):
934 self.basedir = os.path.dirname(self.mktemp())
936 d = self.do_cli("create-alias", "tahoe")
937 def _check((rc, out, err), st):
938 self.failUnlessReallyEqual(rc, 0)
939 self.failUnlessReallyEqual(err, "")
940 self.failUnlessIn(st, out)
942 def _mkdir(ign, mutable_type, uri_prefix, dirname):
943 d2 = self.do_cli("mkdir", "--format="+mutable_type, dirname)
944 d2.addCallback(_check, uri_prefix)
945 def _stash_filecap(cap):
946 u = uri.from_string(cap)
947 fn_uri = u.get_filenode_cap()
948 self._filecap = fn_uri.to_string()
949 d2.addCallback(_stash_filecap)
950 d2.addCallback(lambda ign: self.do_cli("ls", "--json", dirname))
951 d2.addCallback(_check, uri_prefix)
952 d2.addCallback(lambda ign: self.do_cli("ls", "--json", self._filecap))
953 d2.addCallback(_check, '"format": "%s"' % (mutable_type.upper(),))
956 d.addCallback(_mkdir, "sdmf", "URI:DIR2", "tahoe:foo")
957 d.addCallback(_mkdir, "SDMF", "URI:DIR2", "tahoe:foo2")
958 d.addCallback(_mkdir, "mdmf", "URI:DIR2-MDMF", "tahoe:bar")
959 d.addCallback(_mkdir, "MDMF", "URI:DIR2-MDMF", "tahoe:bar2")
962 def test_mkdir_mutable_type_unlinked(self):
963 self.basedir = os.path.dirname(self.mktemp())
965 d = self.do_cli("mkdir", "--format=SDMF")
966 def _check((rc, out, err), st):
967 self.failUnlessReallyEqual(rc, 0)
968 self.failUnlessReallyEqual(err, "")
969 self.failUnlessIn(st, out)
971 d.addCallback(_check, "URI:DIR2")
972 def _stash_dircap(cap):
974 # Now we're going to feed the cap into uri.from_string...
975 u = uri.from_string(cap)
976 # ...grab the underlying filenode uri.
977 fn_uri = u.get_filenode_cap()
979 self._filecap = fn_uri.to_string()
980 d.addCallback(_stash_dircap)
981 d.addCallback(lambda res: self.do_cli("ls", "--json",
983 d.addCallback(_check, '"format": "SDMF"')
984 d.addCallback(lambda res: self.do_cli("mkdir", "--format=MDMF"))
985 d.addCallback(_check, "URI:DIR2-MDMF")
986 d.addCallback(_stash_dircap)
987 d.addCallback(lambda res: self.do_cli("ls", "--json",
989 d.addCallback(_check, '"format": "MDMF"')
992 def test_mkdir_bad_mutable_type(self):
993 o = cli.MakeDirectoryOptions()
994 self.failUnlessRaises(usage.UsageError,
998 def test_mkdir_unicode(self):
999 self.basedir = os.path.dirname(self.mktemp())
1003 motorhead_arg = u"tahoe:Mot\u00F6rhead".encode(get_io_encoding())
1004 except UnicodeEncodeError:
1005 raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.")
1007 d = self.do_cli("create-alias", "tahoe")
1008 d.addCallback(lambda res: self.do_cli("mkdir", motorhead_arg))
1009 def _check((rc, out, err)):
1010 self.failUnlessReallyEqual(rc, 0)
1011 self.failUnlessReallyEqual(err, "")
1012 self.failUnlessIn("URI:", out)
1013 d.addCallback(_check)
1017 def test_mkdir_with_nonexistent_alias(self):
1018 # when invoked with an alias that doesn't exist, 'tahoe mkdir' should
1019 # output a sensible error message rather than a stack trace.
1020 self.basedir = "cli/Mkdir/mkdir_with_nonexistent_alias"
1022 d = self.do_cli("mkdir", "havasu:")
1023 def _check((rc, out, err)):
1024 self.failUnlessReallyEqual(rc, 1)
1025 self.failUnlessIn("error:", err)
1026 self.failUnlessReallyEqual(out, "")
1027 d.addCallback(_check)
1031 class Unlink(GridTestMixin, CLITestMixin, unittest.TestCase):
1034 def _create_test_file(self):
1035 data = "puppies" * 1000
1036 path = os.path.join(self.basedir, "datafile")
1037 fileutil.write(path, data)
1038 self.datafile = path
1040 def test_unlink_without_alias(self):
1041 # 'tahoe unlink' should behave sensibly when invoked without an explicit
1042 # alias before the default 'tahoe' alias has been created.
1043 self.basedir = "cli/Unlink/%s_without_alias" % (self.command,)
1045 d = self.do_cli(self.command, "afile")
1046 def _check((rc, out, err)):
1047 self.failUnlessReallyEqual(rc, 1)
1048 self.failUnlessIn("error:", err)
1049 self.failUnlessReallyEqual(out, "")
1050 d.addCallback(_check)
1052 d.addCallback(lambda ign: self.do_cli(self.command, "afile"))
1053 d.addCallback(_check)
1056 def test_unlink_with_nonexistent_alias(self):
1057 # 'tahoe unlink' should behave sensibly when invoked with an explicit
1058 # alias that doesn't exist.
1059 self.basedir = "cli/Unlink/%s_with_nonexistent_alias" % (self.command,)
1061 d = self.do_cli(self.command, "nonexistent:afile")
1062 def _check((rc, out, err)):
1063 self.failUnlessReallyEqual(rc, 1)
1064 self.failUnlessIn("error:", err)
1065 self.failUnlessIn("nonexistent", err)
1066 self.failUnlessReallyEqual(out, "")
1067 d.addCallback(_check)
1069 d.addCallback(lambda ign: self.do_cli(self.command, "nonexistent:afile"))
1070 d.addCallback(_check)
1073 def test_unlink_without_path(self):
1074 # 'tahoe unlink' should give a sensible error message when invoked without a path.
1075 self.basedir = "cli/Unlink/%s_without_path" % (self.command,)
1077 self._create_test_file()
1078 d = self.do_cli("create-alias", "tahoe")
1079 d.addCallback(lambda ign: self.do_cli("put", self.datafile, "tahoe:test"))
1080 def _do_unlink((rc, out, err)):
1081 self.failUnlessReallyEqual(rc, 0)
1082 self.failUnless(out.startswith("URI:"), out)
1083 return self.do_cli(self.command, out.strip('\n'))
1084 d.addCallback(_do_unlink)
1086 def _check((rc, out, err)):
1087 self.failUnlessReallyEqual(rc, 1)
1088 self.failUnlessIn("'tahoe %s'" % (self.command,), err)
1089 self.failUnlessIn("path must be given", err)
1090 self.failUnlessReallyEqual(out, "")
1091 d.addCallback(_check)
1096 """Test that 'tahoe rm' behaves in the same way as 'tahoe unlink'."""
1100 class Stats(GridTestMixin, CLITestMixin, unittest.TestCase):
1101 def test_empty_directory(self):
1102 self.basedir = "cli/Stats/empty_directory"
1104 c0 = self.g.clients[0]
1106 d = c0.create_dirnode()
1109 self.rooturi = n.get_uri()
1110 d.addCallback(_stash_root)
1112 # make sure we can get stats on an empty directory too
1113 d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
1114 def _check_stats((rc, out, err)):
1115 self.failUnlessReallyEqual(err, "")
1116 self.failUnlessReallyEqual(rc, 0)
1117 lines = out.splitlines()
1118 self.failUnlessIn(" count-immutable-files: 0", lines)
1119 self.failUnlessIn(" count-mutable-files: 0", lines)
1120 self.failUnlessIn(" count-literal-files: 0", lines)
1121 self.failUnlessIn(" count-directories: 1", lines)
1122 self.failUnlessIn(" size-immutable-files: 0", lines)
1123 self.failIfIn("Size Histogram:", lines)
1124 d.addCallback(_check_stats)
1128 def test_stats_without_alias(self):
1129 # when invoked with no explicit alias and before the default 'tahoe'
1130 # alias is created, 'tahoe stats' should output an informative error
1131 # message, not a stack trace.
1132 self.basedir = "cli/Stats/stats_without_alias"
1134 d = self.do_cli("stats")
1135 def _check((rc, out, err)):
1136 self.failUnlessReallyEqual(rc, 1)
1137 self.failUnlessIn("error:", err)
1138 self.failUnlessReallyEqual(out, "")
1139 d.addCallback(_check)
1142 def test_stats_with_nonexistent_alias(self):
1143 # when invoked with an explicit alias that doesn't exist,
1144 # 'tahoe stats' should output a useful error message.
1145 self.basedir = "cli/Stats/stats_with_nonexistent_alias"
1147 d = self.do_cli("stats", "havasu:")
1148 def _check((rc, out, err)):
1149 self.failUnlessReallyEqual(rc, 1)
1150 self.failUnlessIn("error:", err)
1151 self.failUnlessReallyEqual(out, "")
1152 d.addCallback(_check)
1156 class Webopen(GridTestMixin, CLITestMixin, unittest.TestCase):
1157 def test_webopen_with_nonexistent_alias(self):
1158 # when invoked with an alias that doesn't exist, 'tahoe webopen'
1159 # should output an informative error message instead of a stack
1161 self.basedir = "cli/Webopen/webopen_with_nonexistent_alias"
1163 d = self.do_cli("webopen", "fake:")
1164 def _check((rc, out, err)):
1165 self.failUnlessReallyEqual(rc, 1)
1166 self.failUnlessIn("error:", err)
1167 self.failUnlessReallyEqual(out, "")
1168 d.addCallback(_check)
1171 def test_webopen(self):
1172 # TODO: replace with @patch that supports Deferreds.
1174 def call_webbrowser_open(url):
1175 self.failUnlessIn(self.alias_uri.replace(':', '%3A'), url)
1176 self.webbrowser_open_called = True
1178 webbrowser.open = self.old_webbrowser_open
1181 self.old_webbrowser_open = webbrowser.open
1183 webbrowser.open = call_webbrowser_open
1185 self.basedir = "cli/Webopen/webopen"
1187 d = self.do_cli("create-alias", "alias:")
1188 def _check_alias((rc, out, err)):
1189 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1190 self.failUnlessIn("Alias 'alias' created", out)
1191 self.failUnlessReallyEqual(err, "")
1192 self.alias_uri = get_aliases(self.get_clientdir())["alias"]
1193 d.addCallback(_check_alias)
1194 d.addCallback(lambda res: self.do_cli("webopen", "alias:"))
1195 def _check_webopen((rc, out, err)):
1196 self.failUnlessReallyEqual(rc, 0, repr((rc, out, err)))
1197 self.failUnlessReallyEqual(out, "")
1198 self.failUnlessReallyEqual(err, "")
1199 self.failUnless(self.webbrowser_open_called)
1200 d.addCallback(_check_webopen)
1207 class Options(ReallyEqualMixin, unittest.TestCase):
1208 # this test case only looks at argument-processing and simple stuff.
1210 def parse(self, args, stdout=None):
1211 o = runner.Options()
1212 if stdout is not None:
1214 o.parseOptions(args)
1215 while hasattr(o, "subOptions"):
1219 def test_list(self):
1220 fileutil.rm_dir("cli/test_options")
1221 fileutil.make_dirs("cli/test_options")
1222 fileutil.make_dirs("cli/test_options/private")
1223 fileutil.write("cli/test_options/node.url", "http://localhost:8080/\n")
1224 filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16,
1225 fingerprint="\x00"*32)
1226 private_uri = uri.DirectoryURI(filenode_uri).to_string()
1227 fileutil.write("cli/test_options/private/root_dir.cap", private_uri + "\n")
1228 def parse2(args): return parse_options("cli/test_options", "ls", args)
1230 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1231 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1232 self.failUnlessEqual(o.where, u"")
1234 o = parse2(["--node-url", "http://example.org:8111/"])
1235 self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
1236 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1237 self.failUnlessEqual(o.where, u"")
1239 o = parse2(["--dir-cap", "root"])
1240 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1241 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
1242 self.failUnlessEqual(o.where, u"")
1244 other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
1245 fingerprint="\x11"*32)
1246 other_uri = uri.DirectoryURI(other_filenode_uri).to_string()
1247 o = parse2(["--dir-cap", other_uri])
1248 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1249 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1250 self.failUnlessEqual(o.where, u"")
1252 o = parse2(["--dir-cap", other_uri, "subdir"])
1253 self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1254 self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
1255 self.failUnlessEqual(o.where, u"subdir")
1257 self.failUnlessRaises(usage.UsageError, parse2,
1258 ["--node-url", "NOT-A-URL"])
1260 o = parse2(["--node-url", "http://localhost:8080"])
1261 self.failUnlessEqual(o["node-url"], "http://localhost:8080/")
1263 o = parse2(["--node-url", "https://localhost/"])
1264 self.failUnlessEqual(o["node-url"], "https://localhost/")
1266 def test_version(self):
1267 # "tahoe --version" dumps text to stdout and exits
1269 self.failUnlessRaises(SystemExit, self.parse, ["--version"], stdout)
1270 self.failUnlessIn(allmydata.__appname__ + ":", stdout.getvalue())
1271 # but "tahoe SUBCOMMAND --version" should be rejected
1272 self.failUnlessRaises(usage.UsageError, self.parse,
1273 ["start", "--version"])
1274 self.failUnlessRaises(usage.UsageError, self.parse,
1275 ["start", "--version-and-path"])
1277 def test_quiet(self):
1278 # accepted as an overall option, but not on subcommands
1279 o = self.parse(["--quiet", "start"])
1280 self.failUnless(o.parent["quiet"])
1281 self.failUnlessRaises(usage.UsageError, self.parse,
1282 ["start", "--quiet"])
1284 def test_basedir(self):
1285 # accept a --node-directory option before the verb, or a --basedir
1286 # option after, or a basedir argument after, but none in the wrong
1287 # place, and not more than one of the three.
1288 o = self.parse(["start"])
1289 self.failUnlessReallyEqual(o["basedir"], os.path.join(fileutil.abspath_expanduser_unicode(u"~"),
1291 o = self.parse(["start", "here"])
1292 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1293 o = self.parse(["start", "--basedir", "there"])
1294 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1295 o = self.parse(["--node-directory", "there", "start"])
1296 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"there"))
1298 o = self.parse(["start", "here", "--nodaemon"])
1299 self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1301 self.failUnlessRaises(usage.UsageError, self.parse,
1302 ["--basedir", "there", "start"])
1303 self.failUnlessRaises(usage.UsageError, self.parse,
1304 ["start", "--node-directory", "there"])
1306 self.failUnlessRaises(usage.UsageError, self.parse,
1307 ["--node-directory=there",
1308 "start", "--basedir=here"])
1309 self.failUnlessRaises(usage.UsageError, self.parse,
1310 ["start", "--basedir=here", "anywhere"])
1311 self.failUnlessRaises(usage.UsageError, self.parse,
1312 ["--node-directory=there",
1313 "start", "anywhere"])
1314 self.failUnlessRaises(usage.UsageError, self.parse,
1315 ["--node-directory=there",
1316 "start", "--basedir=here", "anywhere"])
1318 self.failUnlessRaises(usage.UsageError, self.parse,
1319 ["--node-directory=there", "start", "--nodaemon"])
1320 self.failUnlessRaises(usage.UsageError, self.parse,
1321 ["start", "--basedir=here", "--nodaemon"])