source: trunk/src/allmydata/test/cli/test_cp.py

Last change on this file was 3fb0bcf, checked in by Itamar Turner-Trauring <itamar@…>, at 2024-02-27T20:37:53Z

Remove unnecessary imports.

  • Property mode set to 100644
File size: 46.1 KB
Line 
1"""
2Ported to Python 3.
3"""
4
5import os.path, json
6from twisted.trial import unittest
7from twisted.python import usage
8from twisted.internet import defer
9
10from allmydata.scripts import cli
11from allmydata.util import fileutil
12from allmydata.util.encodingutil import (quote_output, unicode_to_output, to_bytes)
13from allmydata.util.assertutil import _assert
14from ..no_network import GridTestMixin
15from .common import CLITestMixin
16from ..common_util import skip_if_cannot_represent_filename
17
18class Cp(GridTestMixin, CLITestMixin, unittest.TestCase):
19
20    def test_not_enough_args(self):
21        o = cli.CpOptions()
22        self.failUnlessRaises(usage.UsageError,
23                              o.parseOptions, ["onearg"])
24
25    def test_unicode_filename(self):
26        self.basedir = "cli/Cp/unicode_filename"
27
28        fn1 = os.path.join(self.basedir, u"\u00C4rtonwall")
29        artonwall_arg = u"\u00C4rtonwall"
30
31        skip_if_cannot_represent_filename(fn1)
32
33        self.set_up_grid(oneshare=True)
34
35        DATA1 = "unicode file content"
36        fileutil.write(fn1, DATA1)
37
38        fn2 = os.path.join(self.basedir, "Metallica")
39        DATA2 = "non-unicode file content"
40        fileutil.write(fn2, DATA2)
41
42        d = self.do_cli("create-alias", "tahoe")
43
44        d.addCallback(lambda res: self.do_cli("cp", fn1, "tahoe:"))
45
46        d.addCallback(lambda res: self.do_cli("get", "tahoe:" + artonwall_arg))
47        d.addCallback(lambda rc_out_err: self.assertEqual(rc_out_err[1], DATA1))
48
49        # Version where destination filename is explicitly Unicode too.
50        d.addCallback(lambda res: self.do_cli("cp", fn1, "tahoe:" + artonwall_arg + "-2"))
51        d.addCallback(lambda res: self.do_cli("get", "tahoe:" + artonwall_arg + "-2"))
52        d.addCallback(lambda rc_out_err: self.assertEqual(rc_out_err[1], DATA1))
53
54        d.addCallback(lambda res: self.do_cli("cp", fn2, "tahoe:"))
55
56        d.addCallback(lambda res: self.do_cli("get", "tahoe:Metallica"))
57        d.addCallback(lambda rc_out_err: self.assertEqual(rc_out_err[1], DATA2))
58
59        d.addCallback(lambda res: self.do_cli("ls", "tahoe:"))
60        def _check(args):
61            (rc, out, err) = args
62            try:
63                unicode_to_output(u"\u00C4rtonwall")
64            except UnicodeEncodeError:
65                self.failUnlessReallyEqual(rc, 1)
66                self.failUnlessReallyEqual(out, "Metallica\n")
67                self.failUnlessIn(quote_output(u"\u00C4rtonwall"), err)
68                self.failUnlessIn("files whose names could not be converted", err)
69            else:
70                self.failUnlessReallyEqual(rc, 0)
71                self.failUnlessReallyEqual(out, u"Metallica\n\u00C4rtonwall\n\u00C4rtonwall-2\n")
72                self.assertEqual(len(err), 0, err)
73        d.addCallback(_check)
74
75        return d
76
77    def test_dangling_symlink_vs_recursion(self):
78        if not hasattr(os, 'symlink'):
79            raise unittest.SkipTest("Symlinks are not supported by Python on this platform.")
80
81        # cp -r on a directory containing a dangling symlink shouldn't assert
82        self.basedir = "cli/Cp/dangling_symlink_vs_recursion"
83        self.set_up_grid(oneshare=True)
84        dn = os.path.join(self.basedir, "dir")
85        os.mkdir(dn)
86        fn = os.path.join(dn, "Fakebandica")
87        ln = os.path.join(dn, "link")
88        os.symlink(fn, ln)
89
90        d = self.do_cli("create-alias", "tahoe")
91        d.addCallback(lambda res: self.do_cli("cp", "--recursive",
92                                              dn, "tahoe:"))
93        return d
94
95    def test_copy_using_filecap(self):
96        self.basedir = "cli/Cp/test_copy_using_filecap"
97        self.set_up_grid(oneshare=True)
98        outdir = os.path.join(self.basedir, "outdir")
99        os.mkdir(outdir)
100        fn1 = os.path.join(self.basedir, "Metallica")
101        fn2 = os.path.join(outdir, "Not Metallica")
102        fn3 = os.path.join(outdir, "test2")
103        DATA1 = b"puppies" * 10000
104        fileutil.write(fn1, DATA1)
105
106        d = self.do_cli("create-alias", "tahoe")
107        d.addCallback(lambda ign: self.do_cli("put", fn1))
108        def _put_file(args):
109            (rc, out, err) = args
110            self.failUnlessReallyEqual(rc, 0)
111            self.failUnlessIn("200 OK", err)
112            # keep track of the filecap
113            self.filecap = out.strip()
114        d.addCallback(_put_file)
115
116        # Let's try copying this to the disk using the filecap.
117        d.addCallback(lambda ign: self.do_cli("cp", self.filecap, fn2))
118        def _copy_file(args):
119            (rc, out, err) = args
120            self.failUnlessReallyEqual(rc, 0)
121            results = fileutil.read(fn2)
122            self.failUnlessReallyEqual(results, DATA1)
123        d.addCallback(_copy_file)
124
125        # Test copying a filecap to local dir, which should fail without a
126        # destination filename (#761).
127        d.addCallback(lambda ign: self.do_cli("cp", self.filecap, outdir))
128        def _resp(args):
129            (rc, out, err) = args
130            self.failUnlessReallyEqual(rc, 1)
131            self.failUnlessIn("when copying into a directory, all source files must have names, but",
132                              err)
133            self.assertEqual(len(out), 0, out)
134        d.addCallback(_resp)
135
136        # Create a directory, linked at tahoe:test .
137        d.addCallback(lambda ign: self.do_cli("mkdir", "tahoe:test"))
138        def _get_dir(args):
139            (rc, out, err) = args
140            self.failUnlessReallyEqual(rc, 0)
141            self.dircap = out.strip()
142        d.addCallback(_get_dir)
143
144        # Upload a file to the directory.
145        d.addCallback(lambda ign:
146                      self.do_cli("put", fn1, "tahoe:test/test_file"))
147        d.addCallback(lambda rc_out_err: self.failUnlessReallyEqual(rc_out_err[0], 0))
148
149        # Copying DIRCAP/filename to a local dir should work, because the
150        # destination filename can be inferred.
151        d.addCallback(lambda ign:
152                      self.do_cli("cp",  self.dircap + "/test_file", outdir))
153        def _get_resp(args):
154            (rc, out, err) = args
155            self.failUnlessReallyEqual(rc, 0)
156            results = fileutil.read(os.path.join(outdir, "test_file"))
157            self.failUnlessReallyEqual(results, DATA1)
158        d.addCallback(_get_resp)
159
160        # ... and to an explicit filename different from the source filename.
161        d.addCallback(lambda ign:
162                      self.do_cli("cp",  self.dircap + "/test_file", fn3))
163        def _get_resp2(args):
164            (rc, out, err) = args
165            self.failUnlessReallyEqual(rc, 0)
166            results = fileutil.read(fn3)
167            self.failUnlessReallyEqual(results, DATA1)
168        d.addCallback(_get_resp2)
169
170        # Test that the --verbose option prints correct indices (#1805).
171        d.addCallback(lambda ign:
172                      self.do_cli("cp", "--verbose", fn3, self.dircap))
173        def _test_for_wrong_indices(args):
174            (rc, out, err) = args
175            lines = err.split('\n')
176            self.failUnlessIn('examining 1 of 1', lines)
177            self.failUnlessIn('starting copy, 1 files, 1 directories', lines)
178            self.failIfIn('examining 0 of', err)
179        d.addCallback(_test_for_wrong_indices)
180        return d
181
182    def test_cp_with_nonexistent_alias(self):
183        # when invoked with an alias or aliases that don't exist, 'tahoe cp'
184        # should output a sensible error message rather than a stack trace.
185        self.basedir = "cli/Cp/cp_with_nonexistent_alias"
186        self.set_up_grid(oneshare=True)
187        d = self.do_cli("cp", "fake:file1", "fake:file2")
188        def _check(args):
189            (rc, out, err) = args
190            self.failUnlessReallyEqual(rc, 1)
191            self.failUnlessIn("error:", err)
192        d.addCallback(_check)
193        # 'tahoe cp' actually processes the target argument first, so we need
194        # to check to make sure that validation extends to the source
195        # argument.
196        d.addCallback(lambda ign: self.do_cli("create-alias", "tahoe"))
197        d.addCallback(lambda ign: self.do_cli("cp", "fake:file1",
198                                              "tahoe:file2"))
199        d.addCallback(_check)
200        return d
201
202    def test_unicode_dirnames(self):
203        self.basedir = "cli/Cp/unicode_dirnames"
204
205        fn1 = os.path.join(self.basedir, u"\u00C4rtonwall")
206        artonwall_arg = u"\u00C4rtonwall"
207
208        skip_if_cannot_represent_filename(fn1)
209
210        self.set_up_grid(oneshare=True)
211
212        d = self.do_cli("create-alias", "tahoe")
213        d.addCallback(lambda res: self.do_cli("mkdir", "tahoe:test/" + artonwall_arg))
214        d.addCallback(lambda res: self.do_cli("cp", "-r", "tahoe:test", "tahoe:test2"))
215        d.addCallback(lambda res: self.do_cli("ls", "tahoe:test2/test"))
216        def _check(args):
217            (rc, out, err) = args
218            try:
219                unicode_to_output(u"\u00C4rtonwall")
220            except UnicodeEncodeError:
221                self.failUnlessReallyEqual(rc, 1)
222                self.assertEqual(len(out), 0, out)
223                self.failUnlessIn(quote_output(u"\u00C4rtonwall"), err)
224                self.failUnlessIn("files whose names could not be converted", err)
225            else:
226                self.failUnlessReallyEqual(rc, 0)
227                self.failUnlessReallyEqual(out, u"\u00C4rtonwall\n")
228                self.assertEqual(len(err), 0, err)
229        d.addCallback(_check)
230
231        return d
232
233    @defer.inlineCallbacks
234    def test_cp_duplicate_directories(self):
235        self.basedir = "cli/Cp/cp_duplicate_directories"
236        self.set_up_grid(oneshare=True)
237
238        filename = os.path.join(self.basedir, "file")
239        data = b"abc\xff\x00\xee"
240        with open(filename, "wb") as f:
241            f.write(data)
242
243        yield self.do_cli("create-alias", "tahoe")
244        (rc, out, err) = yield self.do_cli("mkdir", "tahoe:test1")
245        self.assertEqual(rc, 0, (rc, err))
246        dircap = out.strip()
247
248        (rc, out, err) = yield self.do_cli("cp", filename, "tahoe:test1/file")
249        self.assertEqual(rc, 0, (rc, err))
250
251        # Now duplicate dirnode, testing duplicates on destination side:
252        (rc, out, err) = yield self.do_cli(
253            "cp", "--recursive", dircap, "tahoe:test2/")
254        self.assertEqual(rc, 0, (rc, err))
255        (rc, out, err) = yield self.do_cli(
256            "cp", "--recursive", dircap, "tahoe:test3/")
257        self.assertEqual(rc, 0, (rc, err))
258
259        # Now copy to local directory, testing duplicates on origin side:
260        yield self.do_cli("cp", "--recursive", "tahoe:", self.basedir)
261
262        for i in range(1, 4):
263            with open(os.path.join(self.basedir, "test%d" % (i,), "file"), "rb") as f:
264                self.assertEquals(f.read(), data)
265
266    @defer.inlineCallbacks
267    def test_cp_immutable_file(self):
268        self.basedir = "cli/Cp/cp_immutable_file"
269        self.set_up_grid(oneshare=True)
270
271        filename = os.path.join(self.basedir, "source_file")
272        data = b"abc\xff\x00\xee"
273        with open(filename, "wb") as f:
274            f.write(data)
275
276        # Create immutable file:
277        yield self.do_cli("create-alias", "tahoe")
278        (rc, out, _) = yield self.do_cli("put", filename, "tahoe:file1")
279        filecap = out.strip()
280        self.assertEqual(rc, 0)
281
282        # Copy it:
283        (rc, _, _) = yield self.do_cli("cp", "tahoe:file1", "tahoe:file2")
284        self.assertEqual(rc, 0)
285
286        # Make sure resulting file is the same:
287        (rc, _, _) = yield self.do_cli("cp", "--recursive", "--caps-only",
288                                       "tahoe:", self.basedir)
289        self.assertEqual(rc, 0)
290        with open(os.path.join(self.basedir, "file2")) as f:
291            self.assertEqual(f.read().strip(), filecap)
292
293    def test_cp_replaces_mutable_file_contents(self):
294        self.basedir = "cli/Cp/cp_replaces_mutable_file_contents"
295        self.set_up_grid(oneshare=True)
296
297        # Write a test file, which we'll copy to the grid.
298        test_txt_path = os.path.join(self.basedir, "test.txt")
299        test_txt_contents = "foo bar baz"
300        f = open(test_txt_path, "w")
301        f.write(test_txt_contents)
302        f.close()
303
304        d = self.do_cli("create-alias", "tahoe")
305        d.addCallback(lambda ignored:
306            self.do_cli("mkdir", "tahoe:test"))
307        # We have to use 'tahoe put' here because 'tahoe cp' doesn't
308        # know how to make mutable files at the destination.
309        d.addCallback(lambda ignored:
310            self.do_cli("put", "--mutable", test_txt_path, "tahoe:test/test.txt"))
311        d.addCallback(lambda ignored:
312            self.do_cli("get", "tahoe:test/test.txt"))
313        def _check(args):
314            (rc, out, err) = args
315            self.failUnlessEqual(rc, 0)
316            self.failUnlessEqual(out, test_txt_contents)
317        d.addCallback(_check)
318
319        # We'll do ls --json to get the read uri and write uri for the
320        # file we've just uploaded.
321        d.addCallback(lambda ignored:
322            self.do_cli("ls", "--json", "tahoe:test/test.txt"))
323        def _get_test_txt_uris(args):
324            (rc, out, err) = args
325            self.failUnlessEqual(rc, 0)
326            filetype, data = json.loads(out)
327
328            self.failUnlessEqual(filetype, "filenode")
329            self.failUnless(data['mutable'])
330
331            self.failUnlessIn("rw_uri", data)
332            self.rw_uri = to_bytes(data["rw_uri"])
333            self.failUnlessIn("ro_uri", data)
334            self.ro_uri = to_bytes(data["ro_uri"])
335        d.addCallback(_get_test_txt_uris)
336
337        # Now make a new file to copy in place of test.txt.
338        new_txt_path = os.path.join(self.basedir, "new.txt")
339        new_txt_contents = "baz bar foo" * 100000
340        f = open(new_txt_path, "w")
341        f.write(new_txt_contents)
342        f.close()
343
344        # Copy the new file on top of the old file.
345        d.addCallback(lambda ignored:
346            self.do_cli("cp", new_txt_path, "tahoe:test/test.txt"))
347
348        # If we get test.txt now, we should see the new data.
349        d.addCallback(lambda ignored:
350            self.do_cli("get", "tahoe:test/test.txt"))
351        d.addCallback(lambda rc_out_err:
352            self.failUnlessEqual(rc_out_err[1], new_txt_contents))
353        # If we get the json of the new file, we should see that the old
354        # uri is there
355        d.addCallback(lambda ignored:
356            self.do_cli("ls", "--json", "tahoe:test/test.txt"))
357        def _check_json(args):
358            (rc, out, err) = args
359            self.failUnlessEqual(rc, 0)
360            filetype, data = json.loads(out)
361
362            self.failUnlessEqual(filetype, "filenode")
363            self.failUnless(data['mutable'])
364
365            self.failUnlessIn("ro_uri", data)
366            self.failUnlessEqual(to_bytes(data["ro_uri"]), self.ro_uri)
367            self.failUnlessIn("rw_uri", data)
368            self.failUnlessEqual(to_bytes(data["rw_uri"]), self.rw_uri)
369        d.addCallback(_check_json)
370
371        # and, finally, doing a GET directly on one of the old uris
372        # should give us the new contents.
373        d.addCallback(lambda ignored:
374            self.do_cli("get", self.rw_uri))
375        d.addCallback(lambda rc_out_err:
376            self.failUnlessEqual(rc_out_err[1], new_txt_contents))
377        # Now copy the old test.txt without an explicit destination
378        # file. tahoe cp will match it to the existing file and
379        # overwrite it appropriately.
380        d.addCallback(lambda ignored:
381            self.do_cli("cp", test_txt_path, "tahoe:test"))
382        d.addCallback(lambda ignored:
383            self.do_cli("get", "tahoe:test/test.txt"))
384        d.addCallback(lambda rc_out_err:
385            self.failUnlessEqual(rc_out_err[1], test_txt_contents))
386        d.addCallback(lambda ignored:
387            self.do_cli("ls", "--json", "tahoe:test/test.txt"))
388        d.addCallback(_check_json)
389        d.addCallback(lambda ignored:
390            self.do_cli("get", self.rw_uri))
391        d.addCallback(lambda rc_out_err:
392            self.failUnlessEqual(rc_out_err[1], test_txt_contents))
393
394        # Now we'll make a more complicated directory structure.
395        # test2/
396        # test2/mutable1
397        # test2/mutable2
398        # test2/imm1
399        # test2/imm2
400        imm_test_txt_path = os.path.join(self.basedir, "imm_test.txt")
401        imm_test_txt_contents = test_txt_contents * 10000
402        fileutil.write(imm_test_txt_path, imm_test_txt_contents)
403        d.addCallback(lambda ignored:
404            self.do_cli("mkdir", "tahoe:test2"))
405        d.addCallback(lambda ignored:
406            self.do_cli("put", "--mutable", new_txt_path,
407                        "tahoe:test2/mutable1"))
408        d.addCallback(lambda ignored:
409            self.do_cli("put", "--mutable", new_txt_path,
410                        "tahoe:test2/mutable2"))
411        d.addCallback(lambda ignored:
412            self.do_cli('put', new_txt_path, "tahoe:test2/imm1"))
413        d.addCallback(lambda ignored:
414            self.do_cli("put", imm_test_txt_path, "tahoe:test2/imm2"))
415        d.addCallback(lambda ignored:
416            self.do_cli("ls", "--json", "tahoe:test2"))
417        def _process_directory_json(args):
418            (rc, out, err) = args
419            self.failUnlessEqual(rc, 0)
420
421            filetype, data = json.loads(out)
422            self.failUnlessEqual(filetype, "dirnode")
423            self.failUnless(data['mutable'])
424            self.failUnlessIn("children", data)
425            children = data['children']
426
427            # Store the URIs for later use.
428            self.childuris = {}
429            for k in ["mutable1", "mutable2", "imm1", "imm2"]:
430                self.failUnlessIn(k, children)
431                childtype, childdata = children[k]
432                self.failUnlessEqual(childtype, "filenode")
433                if "mutable" in k:
434                    self.failUnless(childdata['mutable'])
435                    self.failUnlessIn("rw_uri", childdata)
436                    uri_key = "rw_uri"
437                else:
438                    self.failIf(childdata['mutable'])
439                    self.failUnlessIn("ro_uri", childdata)
440                    uri_key = "ro_uri"
441                self.childuris[k] = to_bytes(childdata[uri_key])
442        d.addCallback(_process_directory_json)
443        # Now build a local directory to copy into place, like the following:
444        # test2/
445        # test2/mutable1
446        # test2/mutable2
447        # test2/imm1
448        # test2/imm3
449        def _build_local_directory(ignored):
450            test2_path = os.path.join(self.basedir, "test2")
451            fileutil.make_dirs(test2_path)
452            for fn in ("mutable1", "mutable2", "imm1", "imm3"):
453                fileutil.write(os.path.join(test2_path, fn), fn * 1000)
454            self.test2_path = test2_path
455        d.addCallback(_build_local_directory)
456        d.addCallback(lambda ignored:
457            self.do_cli("cp", "-r", self.test2_path, "tahoe:"))
458
459        # We expect that mutable1 and mutable2 are overwritten in-place,
460        # so they'll retain their URIs but have different content.
461        def _process_file_json(args, fn):
462            (rc, out, err) = args
463            self.failUnlessEqual(rc, 0)
464            filetype, data = json.loads(out)
465            self.failUnlessEqual(filetype, "filenode")
466
467            if "mutable" in fn:
468                self.failUnless(data['mutable'])
469                self.failUnlessIn("rw_uri", data)
470                self.failUnlessEqual(to_bytes(data["rw_uri"]), self.childuris[fn])
471            else:
472                self.failIf(data['mutable'])
473                self.failUnlessIn("ro_uri", data)
474                self.failIfEqual(to_bytes(data["ro_uri"]), self.childuris[fn])
475
476        for fn in ("mutable1", "mutable2"):
477            d.addCallback(lambda ignored, fn=fn:
478                self.do_cli("get", "tahoe:test2/%s" % fn))
479            d.addCallback(lambda rc_out_err, fn=fn:
480                self.failUnlessEqual(rc_out_err[1], fn * 1000))
481            d.addCallback(lambda ignored, fn=fn:
482                self.do_cli("ls", "--json", "tahoe:test2/%s" % fn))
483            d.addCallback(_process_file_json, fn=fn)
484
485        # imm1 should have been replaced, so both its uri and content
486        # should be different.
487        d.addCallback(lambda ignored:
488            self.do_cli("get", "tahoe:test2/imm1"))
489        d.addCallback(lambda rc_out_err:
490            self.failUnlessEqual(rc_out_err[1], "imm1" * 1000))
491        d.addCallback(lambda ignored:
492            self.do_cli("ls", "--json", "tahoe:test2/imm1"))
493        d.addCallback(_process_file_json, fn="imm1")
494
495        # imm3 should have been created.
496        d.addCallback(lambda ignored:
497            self.do_cli("get", "tahoe:test2/imm3"))
498        d.addCallback(lambda rc_out_err:
499            self.failUnlessEqual(rc_out_err[1], "imm3" * 1000))
500
501        # imm2 should be exactly as we left it, since our newly-copied
502        # directory didn't contain an imm2 entry.
503        d.addCallback(lambda ignored:
504            self.do_cli("get", "tahoe:test2/imm2"))
505        d.addCallback(lambda rc_out_err:
506            self.failUnlessEqual(rc_out_err[1], imm_test_txt_contents))
507        d.addCallback(lambda ignored:
508            self.do_cli("ls", "--json", "tahoe:test2/imm2"))
509        def _process_imm2_json(args):
510            (rc, out, err) = args
511            self.failUnlessEqual(rc, 0)
512            filetype, data = json.loads(out)
513            self.failUnlessEqual(filetype, "filenode")
514            self.failIf(data['mutable'])
515            self.failUnlessIn("ro_uri", data)
516            self.failUnlessEqual(to_bytes(data["ro_uri"]), self.childuris["imm2"])
517        d.addCallback(_process_imm2_json)
518        return d
519
520    def test_cp_overwrite_readonly_mutable_file(self):
521        # tahoe cp should print an error when asked to overwrite a
522        # mutable file that it can't overwrite.
523        self.basedir = "cli/Cp/overwrite_readonly_mutable_file"
524        self.set_up_grid(oneshare=True)
525
526        # This is our initial file. We'll link its readcap into the
527        # tahoe: alias.
528        test_file_path = os.path.join(self.basedir, "test_file.txt")
529        test_file_contents = "This is a test file."
530        fileutil.write(test_file_path, test_file_contents)
531
532        # This is our replacement file. We'll try and fail to upload it
533        # over the readcap that we linked into the tahoe: alias.
534        replacement_file_path = os.path.join(self.basedir, "replacement.txt")
535        replacement_file_contents = "These are new contents."
536        fileutil.write(replacement_file_path, replacement_file_contents)
537
538        d = self.do_cli("create-alias", "tahoe:")
539        d.addCallback(lambda ignored:
540            self.do_cli("put", "--mutable", test_file_path))
541        def _get_test_uri(args):
542            (rc, out, err) = args
543            self.failUnlessEqual(rc, 0)
544            # this should be a write uri
545            self._test_write_uri = out
546        d.addCallback(_get_test_uri)
547        d.addCallback(lambda ignored:
548            self.do_cli("ls", "--json", self._test_write_uri))
549        def _process_test_json(args):
550            (rc, out, err) = args
551            self.failUnlessEqual(rc, 0)
552            filetype, data = json.loads(out)
553
554            self.failUnlessEqual(filetype, "filenode")
555            self.failUnless(data['mutable'])
556            self.failUnlessIn("ro_uri", data)
557            self._test_read_uri = to_bytes(data["ro_uri"])
558        d.addCallback(_process_test_json)
559        # Now we'll link the readonly URI into the tahoe: alias.
560        d.addCallback(lambda ignored:
561            self.do_cli("ln", self._test_read_uri, "tahoe:test_file.txt"))
562        d.addCallback(lambda rc_out_err:
563            self.failUnlessEqual(rc_out_err[0], 0))
564        # Let's grab the json of that to make sure that we did it right.
565        d.addCallback(lambda ignored:
566            self.do_cli("ls", "--json", "tahoe:"))
567        def _process_tahoe_json(args):
568            (rc, out, err) = args
569            self.failUnlessEqual(rc, 0)
570
571            filetype, data = json.loads(out)
572            self.failUnlessEqual(filetype, "dirnode")
573            self.failUnlessIn("children", data)
574            kiddata = data['children']
575
576            self.failUnlessIn("test_file.txt", kiddata)
577            testtype, testdata = kiddata['test_file.txt']
578            self.failUnlessEqual(testtype, "filenode")
579            self.failUnless(testdata['mutable'])
580            self.failUnlessIn("ro_uri", testdata)
581            self.failUnlessEqual(to_bytes(testdata["ro_uri"]), self._test_read_uri)
582            self.failIfIn("rw_uri", testdata)
583        d.addCallback(_process_tahoe_json)
584        # Okay, now we're going to try uploading another mutable file in
585        # place of that one. We should get an error.
586        d.addCallback(lambda ignored:
587            self.do_cli("cp", replacement_file_path, "tahoe:test_file.txt"))
588        def _check_error_message(args):
589            (rc, out, err) = args
590            self.failUnlessEqual(rc, 1)
591            self.failUnlessIn("replace or update requested with read-only cap", err)
592        d.addCallback(_check_error_message)
593        # Make extra sure that that didn't work.
594        d.addCallback(lambda ignored:
595            self.do_cli("get", "tahoe:test_file.txt"))
596        d.addCallback(lambda rc_out_err:
597            self.failUnlessEqual(rc_out_err[1], test_file_contents))
598        d.addCallback(lambda ignored:
599            self.do_cli("get", self._test_read_uri))
600        d.addCallback(lambda rc_out_err:
601            self.failUnlessEqual(rc_out_err[1], test_file_contents))
602        # Now we'll do it without an explicit destination.
603        d.addCallback(lambda ignored:
604            self.do_cli("cp", test_file_path, "tahoe:"))
605        d.addCallback(_check_error_message)
606        d.addCallback(lambda ignored:
607            self.do_cli("get", "tahoe:test_file.txt"))
608        d.addCallback(lambda rc_out_err:
609            self.failUnlessEqual(rc_out_err[1], test_file_contents))
610        d.addCallback(lambda ignored:
611            self.do_cli("get", self._test_read_uri))
612        d.addCallback(lambda rc_out_err:
613            self.failUnlessEqual(rc_out_err[1], test_file_contents))
614        # Now we'll link a readonly file into a subdirectory.
615        d.addCallback(lambda ignored:
616            self.do_cli("mkdir", "tahoe:testdir"))
617        d.addCallback(lambda rc_out_err:
618            self.failUnlessEqual(rc_out_err[0], 0))
619        d.addCallback(lambda ignored:
620            self.do_cli("ln", self._test_read_uri, "tahoe:test/file2.txt"))
621        d.addCallback(lambda rc_out_err:
622            self.failUnlessEqual(rc_out_err[0], 0))
623
624        test_dir_path = os.path.join(self.basedir, "test")
625        fileutil.make_dirs(test_dir_path)
626        for f in ("file1.txt", "file2.txt"):
627            fileutil.write(os.path.join(test_dir_path, f), f * 10000)
628
629        d.addCallback(lambda ignored:
630            self.do_cli("cp", "-r", test_dir_path, "tahoe:"))
631        d.addCallback(_check_error_message)
632        d.addCallback(lambda ignored:
633            self.do_cli("ls", "--json", "tahoe:test"))
634        def _got_testdir_json(args):
635            (rc, out, err) = args
636            self.failUnlessEqual(rc, 0)
637
638            filetype, data = json.loads(out)
639            self.failUnlessEqual(filetype, "dirnode")
640
641            self.failUnlessIn("children", data)
642            childdata = data['children']
643
644            self.failUnlessIn("file2.txt", childdata)
645            file2type, file2data = childdata['file2.txt']
646            self.failUnlessEqual(file2type, "filenode")
647            self.failUnless(file2data['mutable'])
648            self.failUnlessIn("ro_uri", file2data)
649            self.failUnlessEqual(to_bytes(file2data["ro_uri"]), self._test_read_uri)
650            self.failIfIn("rw_uri", file2data)
651        d.addCallback(_got_testdir_json)
652        return d
653
654    def test_cp_verbose(self):
655        self.basedir = "cli/Cp/cp_verbose"
656        self.set_up_grid(oneshare=True)
657
658        # Write two test files, which we'll copy to the grid.
659        test1_path = os.path.join(self.basedir, "test1")
660        test2_path = os.path.join(self.basedir, "test2")
661        fileutil.write(test1_path, "test1")
662        fileutil.write(test2_path, "test2")
663
664        d = self.do_cli("create-alias", "tahoe")
665        d.addCallback(lambda ign:
666            self.do_cli("cp", "--verbose", test1_path, test2_path, "tahoe:"))
667        def _check(res):
668            (rc, out, err) = res
669            self.failUnlessEqual(rc, 0, str(res))
670            self.failUnlessIn("Success: files copied", out, str(res))
671            self.failUnlessEqual(err, """\
672attaching sources to targets, 2 files / 0 dirs in root
673targets assigned, 1 dirs, 2 files
674starting copy, 2 files, 1 directories
6751/2 files, 0/1 directories
6762/2 files, 0/1 directories
6771/1 directories
678""", str(res))
679        d.addCallback(_check)
680        return d
681
682    def test_cp_copies_dir(self):
683        # This test ensures that a directory is copied using
684        # tahoe cp -r. Refer to ticket #712:
685        # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/712
686
687        self.basedir = "cli/Cp/cp_copies_dir"
688        self.set_up_grid(oneshare=True)
689        subdir = os.path.join(self.basedir, "foo")
690        os.mkdir(subdir)
691        test1_path = os.path.join(subdir, "test1")
692        fileutil.write(test1_path, "test1")
693
694        d = self.do_cli("create-alias", "tahoe")
695        d.addCallback(lambda ign:
696            self.do_cli("cp", "-r", subdir, "tahoe:"))
697        d.addCallback(lambda ign:
698            self.do_cli("ls", "tahoe:"))
699        def _check(res, item):
700            (rc, out, err) = res
701            self.failUnlessEqual(rc, 0)
702            self.failUnlessEqual(err, "")
703            self.failUnlessIn(item, out, str(res))
704        d.addCallback(_check, "foo")
705        d.addCallback(lambda ign:
706            self.do_cli("ls", "tahoe:foo/"))
707        d.addCallback(_check, "test1")
708
709        d.addCallback(lambda ign: fileutil.rm_dir(subdir))
710        d.addCallback(lambda ign: self.do_cli("cp", "-r", "tahoe:foo", self.basedir))
711        def _check_local_fs(ign):
712            self.failUnless(os.path.isdir(self.basedir))
713            self.failUnless(os.path.isfile(test1_path))
714        d.addCallback(_check_local_fs)
715        return d
716
717    def test_ticket_2027(self):
718        # This test ensures that tahoe will copy a file from the grid to
719        # a local directory without a specified file name.
720        # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2027
721        self.basedir = "cli/Cp/ticket_2027"
722        self.set_up_grid(oneshare=True)
723
724        # Write a test file, which we'll copy to the grid.
725        test1_path = os.path.join(self.basedir, "test1")
726        fileutil.write(test1_path, "test1")
727
728        d = self.do_cli("create-alias", "tahoe")
729        d.addCallback(lambda ign:
730            self.do_cli("cp", test1_path, "tahoe:"))
731        d.addCallback(lambda ign:
732            self.do_cli("cp", "tahoe:test1", self.basedir))
733        def _check(res):
734            (rc, out, err) = res
735            self.failUnlessIn("Success: file copied", out, str(res))
736        return d
737
738# these test cases come from ticket #2329 comment 40
739# trailing slash on target *directory* should not matter, test both
740# trailing slash on target files should cause error
741# trailing slash on source directory should not matter, test a few
742# trailing slash on source files should cause error
743
744COPYOUT_TESTCASES = """
745cp    $FILECAP          to/existing-file : to/existing-file
746cp -r $FILECAP          to/existing-file : to/existing-file
747cp    $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file : E6-MANYONE
748cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file : E6-MANYONE
749cp    $DIRCAP           to/existing-file : E4-NEED-R
750cp -r $DIRCAP           to/existing-file : E5-DIRTOFILE
751cp    $FILECAP $DIRCAP  to/existing-file : E4-NEED-R
752cp -r $FILECAP $DIRCAP  to/existing-file : E6-MANYONE
753
754cp    $FILECAP          to/existing-file/ : E7-BADSLASH
755cp -r $FILECAP          to/existing-file/ : E7-BADSLASH
756cp    $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file/ : E7-BADSLASH
757cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file/ : E7-BADSLASH
758cp    $DIRCAP           to/existing-file/ : E4-NEED-R
759cp -r $DIRCAP           to/existing-file/ : E7-BADSLASH
760cp    $FILECAP $DIRCAP  to/existing-file/ : E4-NEED-R
761cp -r $FILECAP $DIRCAP  to/existing-file/ : E7-BADSLASH
762
763# single source to a (present) target directory
764cp    $FILECAP        to : E2-DESTNAME
765cp -r $FILECAP        to : E2-DESTNAME
766cp    $DIRCAP/file    to : to/file
767cp -r $DIRCAP/file    to : to/file
768# these two are errors
769cp    $DIRCAP/file/   to : E8-BADSLASH
770cp -r $DIRCAP/file/   to : E8-BADSLASH
771cp    $PARENTCAP/dir  to : E4-NEED-R
772cp -r $PARENTCAP/dir  to : to/dir/file
773# but these two should ignore the trailing source slash
774cp    $PARENTCAP/dir/ to : E4-NEED-R
775cp -r $PARENTCAP/dir/ to : to/dir/file
776cp    $DIRCAP         to : E4-NEED-R
777cp -r $DIRCAP         to : to/file
778cp    $DIRALIAS       to : E4-NEED-R
779cp -r $DIRALIAS       to : to/file
780
781cp    $FILECAP       to/ : E2-DESTNAME
782cp -r $FILECAP       to/ : E2-DESTNAME
783cp    $DIRCAP/file   to/ : to/file
784cp -r $DIRCAP/file   to/ : to/file
785cp    $PARENTCAP/dir to/ : E4-NEED-R
786cp -r $PARENTCAP/dir to/ : to/dir/file
787cp    $DIRCAP        to/ : E4-NEED-R
788cp -r $DIRCAP        to/ : to/file
789cp    $DIRALIAS      to/ : E4-NEED-R
790cp -r $DIRALIAS      to/ : to/file
791
792# multiple sources to a (present) target directory
793cp    $DIRCAP/file $PARENTCAP/dir2/file2 to : to/file,to/file2
794cp    $DIRCAP/file $FILECAP              to : E2-DESTNAME
795cp    $DIRCAP $FILECAP                   to : E4-NEED-R
796cp -r $DIRCAP $FILECAP                   to : E2-DESTNAME
797      # namedfile, unnameddir, nameddir
798cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to : E4-NEED-R
799cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to : to/file3,to/file,to/dir2/file2
800      # namedfile, unnameddir, nameddir, unnamedfile
801cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to : E4-NEED-R
802cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to : E2-DESTNAME
803
804cp    $DIRCAP/file $PARENTCAP/dir2/file2 to/ : to/file,to/file2
805cp    $DIRCAP/file $FILECAP           to/    : E2-DESTNAME
806cp    $DIRCAP $FILECAP                to/    : E4-NEED-R
807cp -r $DIRCAP $FILECAP                to/    : E2-DESTNAME
808      # namedfile, unnameddir, nameddir
809cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to/ : E4-NEED-R
810cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to/ : to/file3,to/file,to/dir2/file2
811      # namedfile, unnameddir, nameddir, unnamedfile
812cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/ : E4-NEED-R
813cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/ : E2-DESTNAME
814
815# single sources to a missing target: should mkdir or create a file
816cp    $FILECAP       to/missing : to/missing
817cp -r $FILECAP       to/missing : to/missing
818cp    $DIRCAP/file   to/missing : to/missing
819cp -r $DIRCAP/file   to/missing : to/missing
820cp    $PARENTCAP/dir to/missing : E4-NEED-R
821cp -r $PARENTCAP/dir to/missing : to/missing/dir/file
822cp    $DIRCAP        to/missing : E4-NEED-R
823cp -r $DIRCAP        to/missing : to/missing/file
824cp    $DIRALIAS      to/missing : E4-NEED-R
825cp -r $DIRALIAS      to/missing : to/missing/file
826
827cp    $FILECAP       to/missing/ : E7-BADSLASH
828cp -r $FILECAP       to/missing/ : E7-BADSLASH
829cp    $DIRCAP/file   to/missing/ : E7-BADSLASH
830cp -r $DIRCAP/file   to/missing/ : E7-BADSLASH
831cp    $PARENTCAP/dir to/missing/ : E4-NEED-R
832cp -r $PARENTCAP/dir to/missing/ : to/missing/dir/file
833cp    $DIRCAP        to/missing/ : E4-NEED-R
834cp -r $DIRCAP        to/missing/ : to/missing/file
835cp    $DIRALIAS      to/missing/ : E4-NEED-R
836cp -r $DIRALIAS      to/missing/ : to/missing/file
837
838# multiple things to a missing target: should mkdir
839cp    $DIRCAP/file $PARENTCAP/dir2/file2 to/missing : to/missing/file,to/missing/file2
840cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/missing : to/missing/file,to/missing/file2
841cp    $DIRCAP/file $FILECAP              to/missing : E2-DESTNAME
842cp -r $DIRCAP/file $FILECAP              to/missing : E2-DESTNAME
843cp    $DIRCAP $FILECAP                   to/missing : E4-NEED-R
844cp -r $DIRCAP $FILECAP                   to/missing : E2-DESTNAME
845      # namedfile, unnameddir, nameddir
846cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to/missing : E4-NEED-R
847cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to/missing : to/missing/file3,to/missing/file,to/missing/dir2/file2
848      # namedfile, unnameddir, nameddir, unnamedfile
849cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing : E4-NEED-R
850cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing : E2-DESTNAME
851
852cp    $DIRCAP/file $PARENTCAP/dir2/file2 to/missing/ : to/missing/file,to/missing/file2
853cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/missing/ : to/missing/file,to/missing/file2
854cp    $DIRCAP/file $FILECAP           to/missing/    : E2-DESTNAME
855cp -r $DIRCAP/file $FILECAP           to/missing/    : E2-DESTNAME
856cp    $DIRCAP $FILECAP                to/missing/    : E4-NEED-R
857cp -r $DIRCAP $FILECAP                to/missing/    : E2-DESTNAME
858      # namedfile, unnameddir, nameddir
859cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to/missing/ : E4-NEED-R
860cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2          to/missing/ : to/missing/file3,to/missing/file,to/missing/dir2/file2
861      # namedfile, unnameddir, nameddir, unnamedfile
862cp    $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing/ : E4-NEED-R
863cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing/ : E2-DESTNAME
864
865# make sure empty directories are copied too
866cp -r $PARENTCAP/dir4 to  : to/dir4/emptydir/
867cp -r $PARENTCAP/dir4 to/ : to/dir4/emptydir/
868
869# name collisions should cause errors, not overwrites
870cp -r $PARENTCAP/dir6/dir $PARENTCAP/dir5/dir to : E9-COLLIDING-TARGETS
871cp -r $PARENTCAP/dir5/dir $PARENTCAP/dir6/dir to : E9-COLLIDING-TARGETS
872cp -r $DIRCAP6 $DIRCAP5 to : E9-COLLIDING-TARGETS
873cp -r $DIRCAP5 $DIRCAP6 to : E9-COLLIDING-TARGETS
874
875"""
876
877class CopyOut(GridTestMixin, CLITestMixin, unittest.TestCase):
878    FILE_CONTENTS = b"file text"
879    FILE_CONTENTS_5 = b"5"
880    FILE_CONTENTS_6 = b"6"
881
882    def do_setup(self):
883        # first we build a tahoe filesystem that contains:
884        #  $PARENTCAP
885        #  $PARENTCAP/dir  == $DIRCAP == alias:
886        #  $PARENTCAP/dir/file == $FILECAP
887        #  $PARENTCAP/dir2        (named directory)
888        #  $PARENTCAP/dir2/file2
889        #  $PARENTCAP/dir3/file3  (a second named file)
890        #  $PARENTCAP/dir4
891        #  $PARENTCAP/dir4/emptydir/ (an empty directory)
892        #  $PARENTCAP/dir5 == $DIRCAP5
893        #  $PARENTCAP/dir5/dir/collide (contents are "5")
894        #  $PARENTCAP/dir6 == $DIRCAP6
895        #  $PARENTCAP/dir6/dir/collide (contents are "6")
896
897        source_file = os.path.join(self.basedir, "file")
898        fileutil.write(source_file, self.FILE_CONTENTS)
899        source_file_5 = os.path.join(self.basedir, "file5")
900        fileutil.write(source_file_5, self.FILE_CONTENTS_5)
901        source_file_6 = os.path.join(self.basedir, "file6")
902        fileutil.write(source_file_6, self.FILE_CONTENTS_6)
903
904        d = self.do_cli("mkdir")
905        def _stash_parentdircap(res):
906            (rc, out, err) = res
907            self.failUnlessEqual(rc, 0, str(res))
908            self.failUnlessEqual(err, "", str(res))
909            self.PARENTCAP = out.strip()
910            return self.do_cli("mkdir", "%s/dir" % self.PARENTCAP)
911        d.addCallback(_stash_parentdircap)
912        def _stash_dircap(res):
913            (rc, out, err) = res
914            self.failUnlessEqual(rc, 0, str(res))
915            self.failUnlessEqual(err, "", str(res))
916            self.DIRCAP = out.strip()
917            return self.do_cli("add-alias", "ALIAS", self.DIRCAP)
918        d.addCallback(_stash_dircap)
919        d.addCallback(lambda ign:
920            self.do_cli("put", source_file, "%s/dir/file" % self.PARENTCAP))
921        def _stash_filecap(res):
922            (rc, out, err) = res
923            self.failUnlessEqual(rc, 0, str(res))
924            self.failUnlessEqual(err.strip(), "201 Created", str(res))
925            self.FILECAP = out.strip()
926            assert self.FILECAP.startswith("URI:LIT:")
927        d.addCallback(_stash_filecap)
928        d.addCallback(lambda ign:
929            self.do_cli("mkdir", "%s/dir2" % self.PARENTCAP))
930        d.addCallback(lambda ign:
931            self.do_cli("put", source_file, "%s/dir2/file2" % self.PARENTCAP))
932        d.addCallback(lambda ign:
933            self.do_cli("mkdir", "%s/dir3" % self.PARENTCAP))
934        d.addCallback(lambda ign:
935            self.do_cli("put", source_file, "%s/dir3/file3" % self.PARENTCAP))
936        d.addCallback(lambda ign:
937            self.do_cli("mkdir", "%s/dir4" % self.PARENTCAP))
938        d.addCallback(lambda ign:
939            self.do_cli("mkdir", "%s/dir4/emptydir" % self.PARENTCAP))
940
941        d.addCallback(lambda ign:
942            self.do_cli("mkdir", "%s/dir5" % self.PARENTCAP))
943        def _stash_dircap_5(res):
944            (rc, out, err) = res
945            self.failUnlessEqual(rc, 0, str(res))
946            self.failUnlessEqual(err, "", str(res))
947            self.DIRCAP5 = out.strip()
948        d.addCallback(_stash_dircap_5)
949        d.addCallback(lambda ign:
950            self.do_cli("mkdir", "%s/dir5/dir" % self.PARENTCAP))
951        d.addCallback(lambda ign:
952            self.do_cli("put", source_file_5, "%s/dir5/dir/collide" % self.PARENTCAP))
953
954        d.addCallback(lambda ign:
955            self.do_cli("mkdir", "%s/dir6" % self.PARENTCAP))
956        def _stash_dircap_6(res):
957            (rc, out, err) = res
958            self.failUnlessEqual(rc, 0, str(res))
959            self.failUnlessEqual(err, "", str(res))
960            self.DIRCAP6 = out.strip()
961        d.addCallback(_stash_dircap_6)
962        d.addCallback(lambda ign:
963            self.do_cli("mkdir", "%s/dir6/dir" % self.PARENTCAP))
964        d.addCallback(lambda ign:
965            self.do_cli("put", source_file_6, "%s/dir6/dir/collide" % self.PARENTCAP))
966
967        return d
968
969    def check_output(self):
970        # locate the files and directories created (if any) under to/
971        top = os.path.join(self.basedir, "to")
972        results = set()
973        for (dirpath, dirnames, filenames) in os.walk(top):
974            assert dirpath.startswith(top)
975            here = "/".join(dirpath.split(os.sep)[len(top.split(os.sep))-1:])
976            results.add(here+"/")
977            for fn in filenames:
978                contents = fileutil.read(os.path.join(dirpath, fn))
979                if contents == self.FILE_CONTENTS:
980                    results.add("%s/%s" % (here, fn))
981                elif contents == self.FILE_CONTENTS_5:
982                    results.add("%s/%s=5" % (here, fn))
983                elif contents == self.FILE_CONTENTS_6:
984                    results.add("%s/%s=6" % (here, fn))
985        return results
986
987    def run_one_case(self, case):
988        cmd = (case
989               .replace("$PARENTCAP", self.PARENTCAP)
990               .replace("$DIRCAP5", self.DIRCAP5)
991               .replace("$DIRCAP6", self.DIRCAP6)
992               .replace("$DIRCAP", self.DIRCAP)
993               .replace("$DIRALIAS", "ALIAS:")
994               .replace("$FILECAP", self.FILECAP)
995               .split())
996        target = cmd[-1]
997        _assert(target == "to" or target.startswith("to/"), target)
998        cmd[-1] = os.path.abspath(os.path.join(self.basedir, cmd[-1]))
999
1000        # reset
1001        targetdir = os.path.abspath(os.path.join(self.basedir, "to"))
1002        fileutil.rm_dir(targetdir)
1003        os.mkdir(targetdir)
1004
1005        if target.rstrip("/") == "to/existing-file":
1006            fileutil.write(cmd[-1], "existing file contents\n")
1007
1008        # The abspath() for cmd[-1] strips a trailing slash, and we want to
1009        # test what happens when it is present. So put it back.
1010        if target.endswith("/"):
1011            cmd[-1] += "/"
1012
1013        d = self.do_cli(*cmd)
1014        def _check(res):
1015            (rc, out, err) = res
1016            err = err.strip()
1017            if rc == 0:
1018                return self.check_output()
1019            if rc == 1:
1020                self.failUnlessEqual(out, "", str(res))
1021                if "when copying into a directory, all source files must have names, but" in err:
1022                    return set(["E2-DESTNAME"])
1023                if err == "cannot copy directories without --recursive":
1024                    return set(["E4-NEED-R"])
1025                if err == "cannot copy directory into a file":
1026                    return set(["E5-DIRTOFILE"])
1027                if err == "copying multiple things requires target be a directory":
1028                    return set(["E6-MANYONE"])
1029                if err == "target is not a directory, but ends with a slash":
1030                    return set(["E7-BADSLASH"])
1031                if (err.startswith("source ") and
1032                    "is not a directory, but ends with a slash" in err):
1033                    return set(["E8-BADSLASH"])
1034                if err == "cannot copy multiple files with the same name into the same target directory":
1035                    return set(["E9-COLLIDING-TARGETS"])
1036            self.fail("unrecognized error ('%s') %s" % (case, res))
1037        d.addCallback(_check)
1038        return d
1039
1040    def do_one_test(self, case, orig_expected):
1041        expected = set(orig_expected)
1042        printable_expected = ",".join(sorted(expected))
1043        #print("---", case, ":", printable_expected)
1044
1045        for f in orig_expected:
1046            # f is "dir/file" or "dir/sub/file" or "dir/" or "dir/sub/"
1047            # we want all parent directories in the set, with trailing /
1048            pieces = f.rstrip("/").split("/")
1049            for i in range(1,len(pieces)):
1050                parent = "/".join(pieces[:i])
1051                expected.add(parent+"/")
1052
1053        d = self.run_one_case(case)
1054        def _dump(got):
1055            ok = "ok" if got == expected else "FAIL"
1056            printable_got = ",".join(sorted(got))
1057            print("%-31s: got %-19s, want %-19s %s" % (case, printable_got,
1058                                                       printable_expected, ok))
1059            return got
1060        #d.addCallback(_dump)
1061        def _check(got):
1062            self.failUnlessEqual(got, expected, case)
1063        d.addCallback(_check)
1064        return d
1065
1066    def do_tests(self):
1067        # then we run various forms of "cp [-r] TAHOETHING to[/missing]"
1068        # and see what happens.
1069        d = defer.succeed(None)
1070        #print()
1071
1072        for line in COPYOUT_TESTCASES.splitlines():
1073            if "#" in line:
1074                line = line[:line.find("#")]
1075            line = line.strip()
1076            if not line:
1077                continue
1078            case, expected = line.split(":")
1079            case = case.strip()
1080            expected = frozenset(expected.strip().split(","))
1081
1082            d.addCallback(lambda ign, case=case, expected=expected:
1083                          self.do_one_test(case, expected))
1084
1085        return d
1086
1087    def test_cp_out(self):
1088        # test copying all sorts of things out of a tahoe filesystem
1089        self.basedir = "cli_cp/CopyOut/cp_out"
1090        self.set_up_grid(num_servers=1, oneshare=True)
1091
1092        d = self.do_setup()
1093        d.addCallback(lambda ign: self.do_tests())
1094        return d
Note: See TracBrowser for help on using the repository browser.