]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_cli.py
'tahoe cp -r', upon encountering a dangling symlink, would assert out.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_cli.py
1 # coding=utf-8
2
3 import os.path
4 from twisted.trial import unittest
5 from cStringIO import StringIO
6 import urllib
7
8 from allmydata.util import fileutil, hashutil
9 from allmydata import uri
10
11 # Test that the scripts can be imported -- although the actual tests of their functionality are
12 # done by invoking them in a subprocess.
13 from allmydata.scripts import tahoe_ls, tahoe_get, tahoe_put, tahoe_rm, tahoe_cp
14 _hush_pyflakes = [tahoe_ls, tahoe_get, tahoe_put, tahoe_rm, tahoe_cp]
15
16 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases
17
18 from allmydata.scripts import cli, debug, runner
19 from allmydata.test.common import SystemTestMixin
20 from twisted.internet import threads # CLI tests use deferToThread
21
22 class CLI(unittest.TestCase):
23     # this test case only looks at argument-processing and simple stuff.
24     def test_options(self):
25         fileutil.rm_dir("cli/test_options")
26         fileutil.make_dirs("cli/test_options")
27         fileutil.make_dirs("cli/test_options/private")
28         open("cli/test_options/node.url","w").write("http://localhost:8080/\n")
29         filenode_uri = uri.WriteableSSKFileURI(writekey="\x00"*16,
30                                                fingerprint="\x00"*32)
31         private_uri = uri.NewDirectoryURI(filenode_uri).to_string()
32         open("cli/test_options/private/root_dir.cap", "w").write(private_uri + "\n")
33         o = cli.ListOptions()
34         o.parseOptions(["--node-directory", "cli/test_options"])
35         self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
36         self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
37         self.failUnlessEqual(o.where, "")
38
39         o = cli.ListOptions()
40         o.parseOptions(["--node-directory", "cli/test_options",
41                         "--node-url", "http://example.org:8111/"])
42         self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
43         self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
44         self.failUnlessEqual(o.where, "")
45
46         o = cli.ListOptions()
47         o.parseOptions(["--node-directory", "cli/test_options",
48                         "--dir-cap", "root"])
49         self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
50         self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
51         self.failUnlessEqual(o.where, "")
52
53         o = cli.ListOptions()
54         other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
55                                                      fingerprint="\x11"*32)
56         other_uri = uri.NewDirectoryURI(other_filenode_uri).to_string()
57         o.parseOptions(["--node-directory", "cli/test_options",
58                         "--dir-cap", other_uri])
59         self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
60         self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
61         self.failUnlessEqual(o.where, "")
62
63         o = cli.ListOptions()
64         o.parseOptions(["--node-directory", "cli/test_options",
65                         "--dir-cap", other_uri, "subdir"])
66         self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
67         self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
68         self.failUnlessEqual(o.where, "subdir")
69
70     def _dump_cap(self, *args):
71         config = debug.DumpCapOptions()
72         config.stdout,config.stderr = StringIO(), StringIO()
73         config.parseOptions(args)
74         debug.dump_cap(config)
75         self.failIf(config.stderr.getvalue())
76         output = config.stdout.getvalue()
77         return output
78
79     def test_dump_cap_chk(self):
80         key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
81         storage_index = hashutil.storage_index_hash(key)
82         uri_extension_hash = hashutil.uri_extension_hash("stuff")
83         needed_shares = 25
84         total_shares = 100
85         size = 1234
86         u = uri.CHKFileURI(key=key,
87                            uri_extension_hash=uri_extension_hash,
88                            needed_shares=needed_shares,
89                            total_shares=total_shares,
90                            size=size)
91         output = self._dump_cap(u.to_string())
92         self.failUnless("CHK File:" in output, output)
93         self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
94         self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
95         self.failUnless("size: 1234" in output, output)
96         self.failUnless("k/N: 25/100" in output, output)
97         self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
98
99         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
100                                 u.to_string())
101         self.failUnless("client renewal secret: znxmki5zdibb5qlt46xbdvk2t55j7hibejq3i5ijyurkr6m6jkhq" in output, output)
102
103         output = self._dump_cap(u.get_verify_cap().to_string())
104         self.failIf("key: " in output, output)
105         self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
106         self.failUnless("size: 1234" in output, output)
107         self.failUnless("k/N: 25/100" in output, output)
108         self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
109
110         prefixed_u = "http://127.0.0.1/uri/%s" % urllib.quote(u.to_string())
111         output = self._dump_cap(prefixed_u)
112         self.failUnless("CHK File:" in output, output)
113         self.failUnless("key: aaaqeayeaudaocajbifqydiob4" in output, output)
114         self.failUnless("UEB hash: nf3nimquen7aeqm36ekgxomalstenpkvsdmf6fplj7swdatbv5oa" in output, output)
115         self.failUnless("size: 1234" in output, output)
116         self.failUnless("k/N: 25/100" in output, output)
117         self.failUnless("storage index: hdis5iaveku6lnlaiccydyid7q" in output, output)
118
119     def test_dump_cap_lit(self):
120         u = uri.LiteralFileURI("this is some data")
121         output = self._dump_cap(u.to_string())
122         self.failUnless("Literal File URI:" in output, output)
123         self.failUnless("data: this is some data" in output, output)
124
125     def test_dump_cap_ssk(self):
126         writekey = "\x01" * 16
127         fingerprint = "\xfe" * 32
128         u = uri.WriteableSSKFileURI(writekey, fingerprint)
129
130         output = self._dump_cap(u.to_string())
131         self.failUnless("SSK Writeable URI:" in output, output)
132         self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output, output)
133         self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
134         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
135         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
136
137         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
138                                 u.to_string())
139         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
140
141         fileutil.make_dirs("cli/test_dump_cap/private")
142         f = open("cli/test_dump_cap/private/secret", "w")
143         f.write("5s33nk3qpvnj2fw3z4mnm2y6fa\n")
144         f.close()
145         output = self._dump_cap("--client-dir", "cli/test_dump_cap",
146                                 u.to_string())
147         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
148
149         output = self._dump_cap("--client-dir", "cli/test_dump_cap_BOGUS",
150                                 u.to_string())
151         self.failIf("file renewal secret:" in output, output)
152
153         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
154                                 u.to_string())
155         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
156         self.failIf("file renewal secret:" in output, output)
157
158         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
159                                 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
160                                 u.to_string())
161         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
162         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
163         self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
164
165         u = u.get_readonly()
166         output = self._dump_cap(u.to_string())
167         self.failUnless("SSK Read-only URI:" in output, output)
168         self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
169         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
170         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
171
172         u = u.get_verify_cap()
173         output = self._dump_cap(u.to_string())
174         self.failUnless("SSK Verifier URI:" in output, output)
175         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
176         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
177
178     def test_dump_cap_directory(self):
179         writekey = "\x01" * 16
180         fingerprint = "\xfe" * 32
181         u1 = uri.WriteableSSKFileURI(writekey, fingerprint)
182         u = uri.NewDirectoryURI(u1)
183
184         output = self._dump_cap(u.to_string())
185         self.failUnless("Directory Writeable URI:" in output, output)
186         self.failUnless("writekey: aeaqcaibaeaqcaibaeaqcaibae" in output,
187                         output)
188         self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
189         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output,
190                         output)
191         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
192
193         output = self._dump_cap("--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
194                                 u.to_string())
195         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
196
197         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
198                                 u.to_string())
199         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
200         self.failIf("file renewal secret:" in output, output)
201
202         output = self._dump_cap("--nodeid", "tqc35esocrvejvg4mablt6aowg6tl43j",
203                                 "--client-secret", "5s33nk3qpvnj2fw3z4mnm2y6fa",
204                                 u.to_string())
205         self.failUnless("write_enabler: mgcavriox2wlb5eer26unwy5cw56elh3sjweffckkmivvsxtaknq" in output, output)
206         self.failUnless("file renewal secret: arpszxzc2t6kb4okkg7sp765xgkni5z7caavj7lta73vmtymjlxq" in output, output)
207         self.failUnless("lease renewal secret: 7pjtaumrb7znzkkbvekkmuwpqfjyfyamznfz4bwwvmh4nw33lorq" in output, output)
208
209         u = u.get_readonly()
210         output = self._dump_cap(u.to_string())
211         self.failUnless("Directory Read-only URI:" in output, output)
212         self.failUnless("readkey: nvgh5vj2ekzzkim5fgtb4gey5y" in output, output)
213         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
214         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
215
216         u = u.get_verify_cap()
217         output = self._dump_cap(u.to_string())
218         self.failUnless("Directory Verifier URI:" in output, output)
219         self.failUnless("storage index: nt4fwemuw7flestsezvo2eveke" in output, output)
220         self.failUnless("fingerprint: 737p57x6737p57x6737p57x6737p57x6737p57x6737p57x6737a" in output, output)
221
222     def _catalog_shares(self, *basedirs):
223         o = debug.CatalogSharesOptions()
224         o.stdout,o.stderr = StringIO(), StringIO()
225         args = list(basedirs)
226         o.parseOptions(args)
227         debug.catalog_shares(o)
228         out = o.stdout.getvalue()
229         err = o.stderr.getvalue()
230         return out, err
231
232     def test_catalog_shares_error(self):
233         nodedir1 = "cli/test_catalog_shares/node1"
234         sharedir = os.path.join(nodedir1, "storage", "shares", "mq", "mqfblse6m5a6dh45isu2cg7oji")
235         fileutil.make_dirs(sharedir)
236         f = open(os.path.join(sharedir, "8"), "wb")
237         open("cli/test_catalog_shares/node1/storage/shares/mq/not-a-dir", "wb").close()
238         # write a bogus share that looks a little bit like CHK
239         f.write("\x00\x00\x00\x01" + "\xff" * 200) # this triggers an assert
240         f.close()
241
242         nodedir2 = "cli/test_catalog_shares/node2"
243         fileutil.make_dirs(nodedir2)
244         open("cli/test_catalog_shares/node1/storage/shares/not-a-dir", "wb").close()
245
246         # now make sure that the 'catalog-shares' commands survives the error
247         out, err = self._catalog_shares(nodedir1, nodedir2)
248         self.failUnlessEqual(out, "", out)
249         self.failUnless("Error processing " in err,
250                         "didn't see 'error processing' in '%s'" % err)
251         #self.failUnless(nodedir1 in err,
252         #                "didn't see '%s' in '%s'" % (nodedir1, err))
253         # windows mangles the path, and os.path.join isn't enough to make
254         # up for it, so just look for individual strings
255         self.failUnless("node1" in err,
256                         "didn't see 'node1' in '%s'" % err)
257         self.failUnless("mqfblse6m5a6dh45isu2cg7oji" in err,
258                         "didn't see 'mqfblse6m5a6dh45isu2cg7oji' in '%s'" % err)
259
260
261 class CLITestMixin:
262     def do_cli(self, verb, *args, **kwargs):
263         nodeargs = [
264             "--node-directory", self.getdir("client0"),
265             ]
266         argv = [verb] + nodeargs + list(args)
267         stdin = kwargs.get("stdin", "")
268         stdout, stderr = StringIO(), StringIO()
269         d = threads.deferToThread(runner.runner, argv, run_by_human=False,
270                                   stdin=StringIO(stdin),
271                                   stdout=stdout, stderr=stderr)
272         def _done(rc):
273             return rc, stdout.getvalue(), stderr.getvalue()
274         d.addCallback(_done)
275         return d
276
277 class CreateAlias(SystemTestMixin, CLITestMixin, unittest.TestCase):
278
279     def _test_webopen(self, args, expected_url):
280         woo = cli.WebopenOptions()
281         all_args = ["--node-directory", self.getdir("client0")] + list(args)
282         woo.parseOptions(all_args)
283         urls = []
284         rc = cli.webopen(woo, urls.append)
285         self.failUnlessEqual(rc, 0)
286         self.failUnlessEqual(len(urls), 1)
287         self.failUnlessEqual(urls[0], expected_url)
288
289     def test_create(self):
290         self.basedir = os.path.dirname(self.mktemp())
291         d = self.set_up_nodes()
292         d.addCallback(lambda res: self.do_cli("create-alias", "tahoe"))
293         def _done((rc,stdout,stderr)):
294             self.failUnless("Alias 'tahoe' created" in stdout)
295             self.failIf(stderr)
296             aliases = get_aliases(self.getdir("client0"))
297             self.failUnless("tahoe" in aliases)
298             self.failUnless(aliases["tahoe"].startswith("URI:DIR2:"))
299         d.addCallback(_done)
300         d.addCallback(lambda res: self.do_cli("create-alias", "two"))
301
302         def _stash_urls(res):
303             aliases = get_aliases(self.getdir("client0"))
304             node_url_file = os.path.join(self.getdir("client0"), "node.url")
305             nodeurl = open(node_url_file, "r").read().strip()
306             uribase = nodeurl + "uri/"
307             self.tahoe_url = uribase + urllib.quote(aliases["tahoe"])
308             self.tahoe_subdir_url = self.tahoe_url + "/subdir"
309             self.two_url = uribase + urllib.quote(aliases["two"])
310             self.two_uri = aliases["two"]
311         d.addCallback(_stash_urls)
312
313         d.addCallback(lambda res: self.do_cli("create-alias", "two")) # dup
314         def _check_create_duplicate((rc,stdout,stderr)):
315             self.failIfEqual(rc, 0)
316             self.failUnless("Alias 'two' already exists!" in stderr)
317             aliases = get_aliases(self.getdir("client0"))
318             self.failUnlessEqual(aliases["two"], self.two_uri)
319         d.addCallback(_check_create_duplicate)
320
321         d.addCallback(lambda res: self.do_cli("add-alias", "added", self.two_uri))
322         def _check_add((rc,stdout,stderr)):
323             self.failUnlessEqual(rc, 0)
324             self.failUnless("Alias 'added' added" in stdout)
325         d.addCallback(_check_add)
326
327         # check add-alias with a duplicate
328         d.addCallback(lambda res: self.do_cli("add-alias", "two", self.two_uri))
329         def _check_add_duplicate((rc,stdout,stderr)):
330             self.failIfEqual(rc, 0)
331             self.failUnless("Alias 'two' already exists!" in stderr)
332             aliases = get_aliases(self.getdir("client0"))
333             self.failUnlessEqual(aliases["two"], self.two_uri)
334         d.addCallback(_check_add_duplicate)
335
336         def _test_urls(junk):
337             self._test_webopen([], self.tahoe_url)
338             self._test_webopen(["/"], self.tahoe_url)
339             self._test_webopen(["tahoe:"], self.tahoe_url)
340             self._test_webopen(["tahoe:/"], self.tahoe_url)
341             self._test_webopen(["tahoe:subdir"], self.tahoe_subdir_url)
342             self._test_webopen(["tahoe:subdir/"], self.tahoe_subdir_url + '/')
343             self._test_webopen(["tahoe:subdir/file"], self.tahoe_subdir_url + '/file')
344             # if "file" is indeed a file, then the url produced by webopen in
345             # this case is disallowed by the webui. but by design, webopen
346             # passes through the mistake from the user to the resultant
347             # webopened url
348             self._test_webopen(["tahoe:subdir/file/"], self.tahoe_subdir_url + '/file/')
349             self._test_webopen(["two:"], self.two_url)
350         d.addCallback(_test_urls)
351
352         return d
353
354 class Put(SystemTestMixin, CLITestMixin, unittest.TestCase):
355
356     def test_unlinked_immutable_stdin(self):
357         # tahoe get `echo DATA | tahoe put`
358         # tahoe get `echo DATA | tahoe put -`
359
360         self.basedir = self.mktemp()
361         DATA = "data" * 100
362         d = self.set_up_nodes()
363         d.addCallback(lambda res: self.do_cli("put", stdin=DATA))
364         def _uploaded(res):
365             (rc, stdout, stderr) = res
366             self.failUnless("waiting for file data on stdin.." in stderr)
367             self.failUnless("200 OK" in stderr, stderr)
368             self.readcap = stdout
369             self.failUnless(self.readcap.startswith("URI:CHK:"))
370         d.addCallback(_uploaded)
371         d.addCallback(lambda res: self.do_cli("get", self.readcap))
372         def _downloaded(res):
373             (rc, stdout, stderr) = res
374             self.failUnlessEqual(stderr, "")
375             self.failUnlessEqual(stdout, DATA)
376         d.addCallback(_downloaded)
377         d.addCallback(lambda res: self.do_cli("put", "-", stdin=DATA))
378         d.addCallback(lambda (rc,stdout,stderr):
379                       self.failUnlessEqual(stdout, self.readcap))
380         return d
381
382     def test_unlinked_immutable_from_file(self):
383         # tahoe put file.txt
384         # tahoe put ./file.txt
385         # tahoe put /tmp/file.txt
386         # tahoe put ~/file.txt
387         self.basedir = os.path.dirname(self.mktemp())
388         # this will be "allmydata.test.test_cli/Put/test_put_from_file/RANDOM"
389         # and the RANDOM directory will exist. Raw mktemp returns a filename.
390
391         rel_fn = os.path.join(self.basedir, "DATAFILE")
392         abs_fn = os.path.abspath(rel_fn)
393         # we make the file small enough to fit in a LIT file, for speed
394         f = open(rel_fn, "w")
395         f.write("short file")
396         f.close()
397         d = self.set_up_nodes()
398         d.addCallback(lambda res: self.do_cli("put", rel_fn))
399         def _uploaded((rc,stdout,stderr)):
400             readcap = stdout
401             self.failUnless(readcap.startswith("URI:LIT:"))
402             self.readcap = readcap
403         d.addCallback(_uploaded)
404         d.addCallback(lambda res: self.do_cli("put", "./" + rel_fn))
405         d.addCallback(lambda (rc,stdout,stderr):
406                       self.failUnlessEqual(stdout, self.readcap))
407         d.addCallback(lambda res: self.do_cli("put", abs_fn))
408         d.addCallback(lambda (rc,stdout,stderr):
409                       self.failUnlessEqual(stdout, self.readcap))
410         # we just have to assume that ~ is handled properly
411         return d
412
413     def test_immutable_from_file(self):
414         # tahoe put file.txt uploaded.txt
415         # tahoe - uploaded.txt
416         # tahoe put file.txt subdir/uploaded.txt
417         # tahoe put file.txt tahoe:uploaded.txt
418         # tahoe put file.txt tahoe:subdir/uploaded.txt
419         # tahoe put file.txt DIRCAP:./uploaded.txt
420         # tahoe put file.txt DIRCAP:./subdir/uploaded.txt
421         self.basedir = os.path.dirname(self.mktemp())
422
423         rel_fn = os.path.join(self.basedir, "DATAFILE")
424         abs_fn = os.path.abspath(rel_fn)
425         # we make the file small enough to fit in a LIT file, for speed
426         DATA = "short file"
427         DATA2 = "short file two"
428         f = open(rel_fn, "w")
429         f.write(DATA)
430         f.close()
431
432         d = self.set_up_nodes()
433         d.addCallback(lambda res: self.do_cli("create-alias", "tahoe"))
434
435         d.addCallback(lambda res:
436                       self.do_cli("put", rel_fn, "uploaded.txt"))
437         def _uploaded((rc,stdout,stderr)):
438             readcap = stdout.strip()
439             self.failUnless(readcap.startswith("URI:LIT:"))
440             self.failUnless("201 Created" in stderr, stderr)
441             self.readcap = readcap
442         d.addCallback(_uploaded)
443         d.addCallback(lambda res:
444                       self.do_cli("get", "tahoe:uploaded.txt"))
445         d.addCallback(lambda (rc,stdout,stderr):
446                       self.failUnlessEqual(stdout, DATA))
447
448         d.addCallback(lambda res:
449                       self.do_cli("put", "-", "uploaded.txt", stdin=DATA2))
450         def _replaced((rc,stdout,stderr)):
451             readcap = stdout.strip()
452             self.failUnless(readcap.startswith("URI:LIT:"))
453             self.failUnless("200 OK" in stderr, stderr)
454         d.addCallback(_replaced)
455
456         d.addCallback(lambda res:
457                       self.do_cli("put", rel_fn, "subdir/uploaded2.txt"))
458         d.addCallback(lambda res: self.do_cli("get", "subdir/uploaded2.txt"))
459         d.addCallback(lambda (rc,stdout,stderr):
460                       self.failUnlessEqual(stdout, DATA))
461
462         d.addCallback(lambda res:
463                       self.do_cli("put", rel_fn, "tahoe:uploaded3.txt"))
464         d.addCallback(lambda res: self.do_cli("get", "tahoe:uploaded3.txt"))
465         d.addCallback(lambda (rc,stdout,stderr):
466                       self.failUnlessEqual(stdout, DATA))
467
468         d.addCallback(lambda res:
469                       self.do_cli("put", rel_fn, "tahoe:subdir/uploaded4.txt"))
470         d.addCallback(lambda res:
471                       self.do_cli("get", "tahoe:subdir/uploaded4.txt"))
472         d.addCallback(lambda (rc,stdout,stderr):
473                       self.failUnlessEqual(stdout, DATA))
474
475         def _get_dircap(res):
476             self.dircap = get_aliases(self.getdir("client0"))["tahoe"]
477         d.addCallback(_get_dircap)
478
479         d.addCallback(lambda res:
480                       self.do_cli("put", rel_fn,
481                                   self.dircap+":./uploaded5.txt"))
482         d.addCallback(lambda res:
483                       self.do_cli("get", "tahoe:uploaded5.txt"))
484         d.addCallback(lambda (rc,stdout,stderr):
485                       self.failUnlessEqual(stdout, DATA))
486
487         d.addCallback(lambda res:
488                       self.do_cli("put", rel_fn,
489                                   self.dircap+":./subdir/uploaded6.txt"))
490         d.addCallback(lambda res:
491                       self.do_cli("get", "tahoe:subdir/uploaded6.txt"))
492         d.addCallback(lambda (rc,stdout,stderr):
493                       self.failUnlessEqual(stdout, DATA))
494
495         return d
496
497     def test_mutable_unlinked(self):
498         # FILECAP = `echo DATA | tahoe put --mutable`
499         # tahoe get FILECAP, compare against DATA
500         # echo DATA2 | tahoe put - FILECAP
501         # tahoe get FILECAP, compare against DATA2
502         # tahoe put file.txt FILECAP
503         self.basedir = os.path.dirname(self.mktemp())
504         DATA = "data" * 100
505         DATA2 = "two" * 100
506         rel_fn = os.path.join(self.basedir, "DATAFILE")
507         abs_fn = os.path.abspath(rel_fn)
508         DATA3 = "three" * 100
509         f = open(rel_fn, "w")
510         f.write(DATA3)
511         f.close()
512
513         d = self.set_up_nodes()
514
515         d.addCallback(lambda res: self.do_cli("put", "--mutable", stdin=DATA))
516         def _created(res):
517             (rc, stdout, stderr) = res
518             self.failUnless("waiting for file data on stdin.." in stderr)
519             self.failUnless("200 OK" in stderr)
520             self.filecap = stdout
521             self.failUnless(self.filecap.startswith("URI:SSK:"))
522         d.addCallback(_created)
523         d.addCallback(lambda res: self.do_cli("get", self.filecap))
524         d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA))
525
526         d.addCallback(lambda res: self.do_cli("put", "-", self.filecap, stdin=DATA2))
527         def _replaced(res):
528             (rc, stdout, stderr) = res
529             self.failUnless("waiting for file data on stdin.." in stderr)
530             self.failUnless("200 OK" in stderr)
531             self.failUnlessEqual(self.filecap, stdout)
532         d.addCallback(_replaced)
533         d.addCallback(lambda res: self.do_cli("get", self.filecap))
534         d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA2))
535
536         d.addCallback(lambda res: self.do_cli("put", rel_fn, self.filecap))
537         def _replaced2(res):
538             (rc, stdout, stderr) = res
539             self.failUnless("200 OK" in stderr)
540             self.failUnlessEqual(self.filecap, stdout)
541         d.addCallback(_replaced2)
542         d.addCallback(lambda res: self.do_cli("get", self.filecap))
543         d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA3))
544
545         return d
546
547     def test_mutable(self):
548         # echo DATA1 | tahoe put --mutable - uploaded.txt
549         # echo DATA2 | tahoe put - uploaded.txt # should modify-in-place
550         # tahoe get uploaded.txt, compare against DATA2
551
552         self.basedir = os.path.dirname(self.mktemp())
553         DATA1 = "data" * 100
554         fn1 = os.path.join(self.basedir, "DATA1")
555         f = open(fn1, "w")
556         f.write(DATA1)
557         f.close()
558         DATA2 = "two" * 100
559         fn2 = os.path.join(self.basedir, "DATA2")
560         f = open(fn2, "w")
561         f.write(DATA2)
562         f.close()
563
564         d = self.set_up_nodes()
565         d.addCallback(lambda res: self.do_cli("create-alias", "tahoe"))
566         d.addCallback(lambda res:
567                       self.do_cli("put", "--mutable", fn1, "tahoe:uploaded.txt"))
568         d.addCallback(lambda res:
569                       self.do_cli("put", fn2, "tahoe:uploaded.txt"))
570         d.addCallback(lambda res:
571                       self.do_cli("get", "tahoe:uploaded.txt"))
572         d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA2))
573         return d
574
575 class Cp(SystemTestMixin, CLITestMixin, unittest.TestCase):
576     def test_unicode_filename(self):
577         self.basedir = os.path.dirname(self.mktemp())
578
579         fn1 = os.path.join(self.basedir, "Ă„rtonwall")
580         DATA1 = "unicode file content"
581         open(fn1, "wb").write(DATA1)
582
583         fn2 = os.path.join(self.basedir, "Metallica")
584         DATA2 = "non-unicode file content"
585         open(fn2, "wb").write(DATA2)
586
587         # Bug #534
588         # Assure that uploading a file whose name contains unicode character doesn't
589         # prevent further uploads in the same directory
590         d = self.set_up_nodes()
591         d.addCallback(lambda res: self.do_cli("create-alias", "tahoe"))
592         d.addCallback(lambda res: self.do_cli("cp", fn1, "tahoe:"))
593         d.addCallback(lambda res: self.do_cli("cp", fn2, "tahoe:"))
594
595         d.addCallback(lambda res: self.do_cli("get", "tahoe:Ă„rtonwall"))
596         d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA1))
597
598         d.addCallback(lambda res: self.do_cli("get", "tahoe:Metallica"))
599         d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA2))
600
601         return d
602     test_unicode_filename.todo = "This behavior is not yet supported, although it does happen to work (for reasons that are ill-understood) on many platforms.  See issue ticket #534."
603
604     def test_dangling_symlink_vs_recursion(self):
605         # cp -r on a directory containing a dangling symlink shouldn't assert
606         self.basedir = os.path.dirname(self.mktemp())
607         dn = os.path.join(self.basedir, "dir")
608         os.mkdir(dn)
609         fn = os.path.join(dn, "Fakebandica")
610         ln = os.path.join(dn, "link")
611         os.symlink(fn, ln)
612
613         d = self.set_up_nodes()
614         d.addCallback(lambda res: self.do_cli("create-alias", "tahoe"))
615         d.addCallback(lambda res: self.do_cli("cp", "--recursive",
616                                               dn, "tahoe:"))
617         return d