]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_cli.py
bed358711097576337996af159f675ba7e4400ab
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_cli.py
1
2 import os.path
3 from cStringIO import StringIO
4 import urllib, sys
5
6 from twisted.trial import unittest
7 from twisted.python.monkey import MonkeyPatcher
8
9 import allmydata
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
18
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]
26
27 from allmydata.scripts import common
28 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases, get_alias, \
29      DefaultAliasMarker
30
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
36
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
40
41 timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s
42
43 def parse_options(basedir, command, args):
44     o = runner.Options()
45     o.parseOptions(["--node-directory", basedir, command] + args)
46     while hasattr(o, "subOptions"):
47         o = o.subOptions
48     return o
49
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)
54
55         # client_num is used to execute client CLI commands on a specific client.
56         client_num = kwargs.get("client_num", 0)
57
58         nodeargs = [
59             "--node-directory", unicode_to_argv(self.get_clientdir(i=client_num)),
60             ]
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)
67         def _done(rc):
68             return rc, stdout.getvalue(), stderr.getvalue()
69         d.addCallback(_done)
70         return d
71
72     def skip_if_cannot_represent_filename(self, u):
73         precondition(isinstance(u, unicode))
74
75         enc = get_filesystem_encoding()
76         if not unicode_platform():
77             try:
78                 u.encode(enc)
79             except UnicodeEncodeError:
80                 raise unittest.SkipTest("A non-ASCII filename could not be encoded on this platform.")
81
82
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()
91         return output
92
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")
96         needed_shares = 25
97         total_shares = 100
98         size = 1234
99         u = uri.CHKFileURI(key=key,
100                            uri_extension_hash=uri_extension_hash,
101                            needed_shares=needed_shares,
102                            total_shares=total_shares,
103                            size=size)
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)
111
112         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
113                                 u.to_string())
114         self.failUnless("client renewal secret: znxmki5zdibb5qlt46xbdvk2t55j7hibejq3i5ijyurkr6m6jkhq" in output, output)
115
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)
122
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)
131
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)
137
138     def test_dump_cap_sdmf(self):
139         writekey = "\x01" * 16
140         fingerprint = "\xfe" * 32
141         u = uri.WriteableSSKFileURI(writekey, fingerprint)
142
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)
149
150         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
151                                 u.to_string())
152         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
153
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",
157                                 u.to_string())
158         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
159
160         output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
161                                 u.to_string())
162         self.failIf("file renewal secret:" in output, output)
163
164         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
165                                 u.to_string())
166         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
167         self.failIf("file renewal secret:" in output, output)
168
169         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
170                                 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
171                                 u.to_string())
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)
175
176         u = u.get_readonly()
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)
182
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)
188
189     def test_dump_cap_mdmf(self):
190         writekey = "\x01" * 16
191         fingerprint = "\xfe" * 32
192         u = uri.WriteableMDMFFileURI(writekey, fingerprint)
193
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)
200
201         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
202                                 u.to_string())
203         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
204
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",
208                                 u.to_string())
209         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
210
211         output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
212                                 u.to_string())
213         self.failIf("file renewal secret:" in output, output)
214
215         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
216                                 u.to_string())
217         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
218         self.failIf("file renewal secret:" in output, output)
219
220         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
221                                 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
222                                 u.to_string())
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)
226
227         u = u.get_readonly()
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)
233
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)
239
240
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")
244         needed_shares = 25
245         total_shares = 100
246         size = 1234
247         u1 = uri.CHKFileURI(key=key,
248                             uri_extension_hash=uri_extension_hash,
249                             needed_shares=needed_shares,
250                             total_shares=total_shares,
251                             size=size)
252         u = uri.ImmutableDirectoryURI(u1)
253
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)
261
262         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
263                                 u.to_string())
264         self.failUnless("file renewal secret: csrvkjgomkyyyil5yo4yk5np37p6oa2ve2hg6xmk2dy7kaxsu6xq" in output, output)
265
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)
274
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)
280
281         output = self._dump_cap(u.to_string())
282         self.failUnless("Directory Writeable URI:" in output, output)
283         self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
284                         output)
285         self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
286         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
287                         output)
288         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
289
290         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
291                                 u.to_string())
292         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
293
294         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
295                                 u.to_string())
296         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
297         self.failIf("file renewal secret:" in output, output)
298
299         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
300                                 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
301                                 u.to_string())
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)
305
306         u = u.get_readonly()
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)
312
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)
318
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)
324
325         output = self._dump_cap(u.to_string())
326         self.failUnless("Directory Writeable URI:" in output, output)
327         self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
328                         output)
329         self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
330         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
331                         output)
332         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
333
334         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
335                                 u.to_string())
336         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
337
338         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
339                                 u.to_string())
340         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
341         self.failIf("file renewal secret:" in output, output)
342
343         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
344                                 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
345                                 u.to_string())
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)
349
350         u = u.get_readonly()
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)
356
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)
362
363
364     def _catalog_shares(self, *basedirs):
365         o = debug.CatalogSharesOptions()
366         o.stdout,o.stderr = StringIO(), StringIO()
367         args = list(basedirs)
368         o.parseOptions(args)
369         debug.catalog_shares(o)
370         out = o.stdout.getvalue()
371         err = o.stderr.getvalue()
372         return out, err
373
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
382
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", "")
386
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)
400
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,
408                    "work": WA,
409                    "c": CA}
410         def ga1(path):
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")
435
436         def ga2(path):
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.
450         if uses_lettercolon:
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"))
455         else:
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")
470
471         def ga3(path):
472             old = common.pretend_platform_uses_lettercolon
473             try:
474                 common.pretend_platform_uses_lettercolon = True
475                 retval = get_alias(aliases, path, None)
476             finally:
477                 common.pretend_platform_uses_lettercolon = old
478             return retval
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.
505         def ga4(path):
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/")
509
510         def ga5(path):
511             old = common.pretend_platform_uses_lettercolon
512             try:
513                 common.pretend_platform_uses_lettercolon = True
514                 retval = get_alias(aliases, path, u"baddefault:")
515             finally:
516                 common.pretend_platform_uses_lettercolon = old
517             return retval
518         self.failUnlessRaises(common.UnknownAliasError, ga5, u"C:\\Windows")
519
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"}
526         def ga1(path):
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")
532
533     def test_listdir_unicode_good(self):
534         filenames = [u'L\u00F4zane', u'Bern', u'Gen\u00E8ve']  # must be NFC
535
536         for name in filenames:
537             self.skip_if_cannot_represent_filename(name)
538
539         basedir = "cli/common/listdir_unicode_good"
540         fileutil.make_dirs(basedir)
541
542         for name in filenames:
543             open(os.path.join(unicode(basedir), name), "wb").close()
544
545         for file in listdir_unicode(unicode(basedir)):
546             self.failUnlessIn(normalize(file), filenames)
547
548     def test_exception_catcher(self):
549         self.basedir = "cli/exception_catcher"
550
551         stderr = StringIO()
552         exc = Exception("canary")
553         ns = Namespace()
554
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)
559             raise exc
560
561         ns.sys_exit_called = False
562         def call_sys_exit(exitcode):
563             ns.sys_exit_called = True
564             self.failUnlessEqual(exitcode, 1)
565
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)
571
572         self.failUnless(ns.runner_called)
573         self.failUnless(ns.sys_exit_called)
574         self.failUnlessIn(str(exc), stderr.getvalue())
575
576
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", " "))
582
583     def test_get(self):
584         help = str(cli.GetOptions())
585         self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help)
586         self.failUnlessIn("% tahoe get FOO |less", help)
587
588     def test_put(self):
589         help = str(cli.PutOptions())
590         self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help)
591         self.failUnlessIn("% cat FILE | tahoe put", help)
592
593     def test_ls(self):
594         help = str(cli.ListOptions())
595         self.failUnlessIn("[options] [PATH]", help)
596
597     def test_unlink(self):
598         help = str(cli.UnlinkOptions())
599         self.failUnlessIn("[options] REMOTE_FILE", help)
600
601     def test_rm(self):
602         help = str(cli.RmOptions())
603         self.failUnlessIn("[options] REMOTE_FILE", help)
604
605     def test_mv(self):
606         help = str(cli.MvOptions())
607         self.failUnlessIn("[options] FROM TO", help)
608         self.failUnlessInNormalized("Use 'tahoe mv' to move files", help)
609
610     def test_cp(self):
611         help = str(cli.CpOptions())
612         self.failUnlessIn("[options] FROM.. TO", help)
613         self.failUnlessInNormalized("Use 'tahoe cp' to copy files", help)
614
615     def test_ln(self):
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)
619
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)
624
625     def test_backup(self):
626         help = str(cli.BackupOptions())
627         self.failUnlessIn("[options] FROM ALIAS:TO", help)
628
629     def test_webopen(self):
630         help = str(cli.WebopenOptions())
631         self.failUnlessIn("[options] [ALIAS:PATH]", help)
632
633     def test_manifest(self):
634         help = str(cli.ManifestOptions())
635         self.failUnlessIn("[options] [ALIAS:PATH]", help)
636
637     def test_stats(self):
638         help = str(cli.StatsOptions())
639         self.failUnlessIn("[options] [ALIAS:PATH]", help)
640
641     def test_check(self):
642         help = str(cli.CheckOptions())
643         self.failUnlessIn("[options] [ALIAS:PATH]", help)
644
645     def test_deep_check(self):
646         help = str(cli.DeepCheckOptions())
647         self.failUnlessIn("[options] [ALIAS:PATH]", help)
648
649     def test_create_alias(self):
650         help = str(cli.CreateAliasOptions())
651         self.failUnlessIn("[options] ALIAS[:]", help)
652
653     def test_add_alias(self):
654         help = str(cli.AddAliasOptions())
655         self.failUnlessIn("[options] ALIAS[:] DIRCAP", help)
656
657     def test_list_aliases(self):
658         help = str(cli.ListAliasesOptions())
659         self.failUnlessIn("[options]", help)
660
661     def test_start(self):
662         help = str(startstop_node.StartOptions())
663         self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
664
665     def test_stop(self):
666         help = str(startstop_node.StopOptions())
667         self.failUnlessIn("[options] [NODEDIR]", help)
668
669     def test_restart(self):
670         help = str(startstop_node.RestartOptions())
671         self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
672
673     def test_run(self):
674         help = str(startstop_node.RunOptions())
675         self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help)
676
677     def test_create_client(self):
678         help = str(create_node.CreateClientOptions())
679         self.failUnlessIn("[options] [NODEDIR]", help)
680
681     def test_create_node(self):
682         help = str(create_node.CreateNodeOptions())
683         self.failUnlessIn("[options] [NODEDIR]", help)
684
685     def test_create_introducer(self):
686         help = str(create_node.CreateIntroducerOptions())
687         self.failUnlessIn("[options] NODEDIR", help)
688
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)
693
694     def test_debug_flogtool(self):
695         options = debug.FlogtoolOptions()
696         help = str(options)
697         self.failUnlessIn(" [global-options] debug flogtool ", help)
698         self.failUnlessInNormalized("The 'tahoe debug flogtool' command uses the correct imports", help)
699
700         for (option, shortcut, oClass, desc) in options.subCommands:
701             subhelp = str(oClass())
702             self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp)
703
704
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)
710         self.datafile = path
711
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
715         # a stack trace
716         self.basedir = "cli/Ln/ln_without_alias"
717         self.set_up_grid()
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,
728                                               "havasu:from"))
729         d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "to"))
730         d.addCallback(_check)
731         return d
732
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"
737         self.set_up_grid()
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,
748                                               "havasu:from"))
749         d.addCallback(lambda ign: self.do_cli("ln", "havasu:from", "huron:to"))
750         d.addCallback(_check)
751         return d
752
753
754 class Admin(unittest.TestCase):
755     def do_cli(self, *args, **kwargs):
756         argv = list(args)
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)
762         def _done(res):
763             return stdout.getvalue(), stderr.getvalue()
764         d.addCallback(_done)
765         return d
766
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)
783         d.addCallback(_done)
784         return d
785
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)
799         d.addCallback(_done)
800         return d
801
802
803 class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
804     def test_get(self):
805         self.basedir = "cli/Errors/get"
806         self.set_up_grid()
807         c0 = self.g.clients[0]
808         self.fileurls = {}
809         DATA = "data" * 100
810         d = c0.upload(upload.Data(DATA, convergence=""))
811         def _stash_bad(ur):
812             self.uri_1share = ur.get_uri()
813             self.delete_shares_numbered(ur.get_uri(), range(1,10))
814         d.addCallback(_stash_bad)
815
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"
821
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,
828                             err)
829         d.addCallback(_check1)
830
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,
838                             err)
839             self.failIf(os.path.exists(targetf))
840         d.addCallback(_check2)
841
842         return d
843
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"
848         self.set_up_grid()
849
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)
855
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)
861         return d
862
863
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"
870         self.set_up_grid()
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)
877         return d
878
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"
883         self.set_up_grid()
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)
891         return d
892
893
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
898         # missing.
899         self.basedir = "cli/Manifest/manifest_without_alias"
900         self.set_up_grid()
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)
907         return d
908
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"
913         self.set_up_grid()
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)
921         return d
922
923
924 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase):
925     def test_mkdir(self):
926         self.basedir = os.path.dirname(self.mktemp())
927         self.set_up_grid()
928
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)
936
937         return d
938
939     def test_mkdir_mutable_type(self):
940         self.basedir = os.path.dirname(self.mktemp())
941         self.set_up_grid()
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)
947             return 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(),))
960             return d2
961
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")
966         return d
967
968     def test_mkdir_mutable_type_unlinked(self):
969         self.basedir = os.path.dirname(self.mktemp())
970         self.set_up_grid()
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)
976             return out
977         d.addCallback(_check, "URI:DIR2")
978         def _stash_dircap(cap):
979             self._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()
984             # ...and stash that.
985             self._filecap = fn_uri.to_string()
986         d.addCallback(_stash_dircap)
987         d.addCallback(lambda res: self.do_cli("ls", "--json",
988                                               self._filecap))
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",
994                                               self._filecap))
995         d.addCallback(_check, '"format": "MDMF"')
996         return d
997
998     def test_mkdir_bad_mutable_type(self):
999         o = cli.MakeDirectoryOptions()
1000         self.failUnlessRaises(usage.UsageError,
1001                               o.parseOptions,
1002                               ["--format=LDMF"])
1003
1004     def test_mkdir_unicode(self):
1005         self.basedir = os.path.dirname(self.mktemp())
1006         self.set_up_grid()
1007
1008         try:
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.")
1012
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)
1020
1021         return d
1022
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"
1027         self.set_up_grid()
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)
1034         return d
1035
1036
1037 class Unlink(GridTestMixin, CLITestMixin, unittest.TestCase):
1038     command = "unlink"
1039
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
1045
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,)
1050         self.set_up_grid()
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)
1057
1058         d.addCallback(lambda ign: self.do_cli(self.command, "afile"))
1059         d.addCallback(_check)
1060         return d
1061
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,)
1066         self.set_up_grid()
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)
1074
1075         d.addCallback(lambda ign: self.do_cli(self.command, "nonexistent:afile"))
1076         d.addCallback(_check)
1077         return d
1078
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,)
1082         self.set_up_grid()
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)
1091
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)
1098         return d
1099
1100
1101 class Rm(Unlink):
1102     """Test that 'tahoe rm' behaves in the same way as 'tahoe unlink'."""
1103     command = "rm"
1104
1105
1106 class Stats(GridTestMixin, CLITestMixin, unittest.TestCase):
1107     def test_empty_directory(self):
1108         self.basedir = "cli/Stats/empty_directory"
1109         self.set_up_grid()
1110         c0 = self.g.clients[0]
1111         self.fileurls = {}
1112         d = c0.create_dirnode()
1113         def _stash_root(n):
1114             self.rootnode = n
1115             self.rooturi = n.get_uri()
1116         d.addCallback(_stash_root)
1117
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)
1131
1132         return d
1133
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"
1139         self.set_up_grid()
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)
1146         return d
1147
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"
1152         self.set_up_grid()
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)
1159         return d
1160
1161
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
1166         # trace.
1167         self.basedir = "cli/Webopen/webopen_with_nonexistent_alias"
1168         self.set_up_grid()
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)
1175         return d
1176
1177     def test_webopen(self):
1178         # TODO: replace with @patch that supports Deferreds.
1179         import webbrowser
1180         def call_webbrowser_open(url):
1181             self.failUnlessIn(self.alias_uri.replace(':', '%3A'), url)
1182             self.webbrowser_open_called = True
1183         def _cleanup(res):
1184             webbrowser.open = self.old_webbrowser_open
1185             return res
1186
1187         self.old_webbrowser_open = webbrowser.open
1188         try:
1189             webbrowser.open = call_webbrowser_open
1190
1191             self.basedir = "cli/Webopen/webopen"
1192             self.set_up_grid()
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)
1207             d.addBoth(_cleanup)
1208         except:
1209             _cleanup(None)
1210             raise
1211         return d
1212
1213 class Options(ReallyEqualMixin, unittest.TestCase):
1214     # this test case only looks at argument-processing and simple stuff.
1215
1216     def parse(self, args, stdout=None):
1217         o = runner.Options()
1218         if stdout is not None:
1219             o.stdout = stdout
1220         o.parseOptions(args)
1221         while hasattr(o, "subOptions"):
1222             o = o.subOptions
1223         return o
1224
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)
1235         o = parse2([])
1236         self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
1237         self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
1238         self.failUnlessEqual(o.where, u"")
1239
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"")
1244
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"")
1249
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"")
1257
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")
1262
1263         self.failUnlessRaises(usage.UsageError, parse2,
1264                               ["--node-url", "NOT-A-URL"])
1265
1266         o = parse2(["--node-url", "http://localhost:8080"])
1267         self.failUnlessEqual(o["node-url"], "http://localhost:8080/")
1268
1269         o = parse2(["--node-url", "https://localhost/"])
1270         self.failUnlessEqual(o["node-url"], "https://localhost/")
1271
1272     def test_version(self):
1273         # "tahoe --version" dumps text to stdout and exits
1274         stdout = StringIO()
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"])
1282
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"])
1289
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"~"),
1296                                                               u".tahoe"))
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"))
1303
1304         o = self.parse(["start", "here", "--nodaemon"])
1305         self.failUnlessReallyEqual(o["basedir"], fileutil.abspath_expanduser_unicode(u"here"))
1306
1307         self.failUnlessRaises(usage.UsageError, self.parse,
1308                               ["--basedir", "there", "start"])
1309         self.failUnlessRaises(usage.UsageError, self.parse,
1310                               ["start", "--node-directory", "there"])
1311
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"])
1323
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"])