import os.path, simplejson from twisted.trial import unittest from twisted.python import usage from twisted.internet import defer from allmydata.scripts import cli from allmydata.util import fileutil from allmydata.util.encodingutil import (quote_output, get_io_encoding, unicode_to_output, to_str) from allmydata.util.assertutil import _assert from .no_network import GridTestMixin from .test_cli import CLITestMixin timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s class Cp(GridTestMixin, CLITestMixin, unittest.TestCase): def test_not_enough_args(self): o = cli.CpOptions() self.failUnlessRaises(usage.UsageError, o.parseOptions, ["onearg"]) def test_unicode_filename(self): self.basedir = "cli/Cp/unicode_filename" fn1 = os.path.join(unicode(self.basedir), u"\u00C4rtonwall") try: fn1_arg = fn1.encode(get_io_encoding()) artonwall_arg = u"\u00C4rtonwall".encode(get_io_encoding()) except UnicodeEncodeError: raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.") self.skip_if_cannot_represent_filename(fn1) self.set_up_grid() DATA1 = "unicode file content" fileutil.write(fn1, DATA1) fn2 = os.path.join(self.basedir, "Metallica") DATA2 = "non-unicode file content" fileutil.write(fn2, DATA2) d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda res: self.do_cli("cp", fn1_arg, "tahoe:")) d.addCallback(lambda res: self.do_cli("get", "tahoe:" + artonwall_arg)) d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA1)) d.addCallback(lambda res: self.do_cli("cp", fn2, "tahoe:")) d.addCallback(lambda res: self.do_cli("get", "tahoe:Metallica")) d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2)) d.addCallback(lambda res: self.do_cli("ls", "tahoe:")) def _check((rc, out, err)): try: unicode_to_output(u"\u00C4rtonwall") except UnicodeEncodeError: self.failUnlessReallyEqual(rc, 1) self.failUnlessReallyEqual(out, "Metallica\n") self.failUnlessIn(quote_output(u"\u00C4rtonwall"), err) self.failUnlessIn("files whose names could not be converted", err) else: self.failUnlessReallyEqual(rc, 0) self.failUnlessReallyEqual(out.decode(get_io_encoding()), u"Metallica\n\u00C4rtonwall\n") self.failUnlessReallyEqual(err, "") d.addCallback(_check) return d def test_dangling_symlink_vs_recursion(self): if not hasattr(os, 'symlink'): raise unittest.SkipTest("Symlinks are not supported by Python on this platform.") # cp -r on a directory containing a dangling symlink shouldn't assert self.basedir = "cli/Cp/dangling_symlink_vs_recursion" self.set_up_grid() dn = os.path.join(self.basedir, "dir") os.mkdir(dn) fn = os.path.join(dn, "Fakebandica") ln = os.path.join(dn, "link") os.symlink(fn, ln) d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda res: self.do_cli("cp", "--recursive", dn, "tahoe:")) return d def test_copy_using_filecap(self): self.basedir = "cli/Cp/test_copy_using_filecap" self.set_up_grid() outdir = os.path.join(self.basedir, "outdir") os.mkdir(outdir) fn1 = os.path.join(self.basedir, "Metallica") fn2 = os.path.join(outdir, "Not Metallica") fn3 = os.path.join(outdir, "test2") DATA1 = "puppies" * 10000 fileutil.write(fn1, DATA1) d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda ign: self.do_cli("put", fn1)) def _put_file((rc, out, err)): self.failUnlessReallyEqual(rc, 0) self.failUnlessIn("200 OK", err) # keep track of the filecap self.filecap = out.strip() d.addCallback(_put_file) # Let's try copying this to the disk using the filecap. d.addCallback(lambda ign: self.do_cli("cp", self.filecap, fn2)) def _copy_file((rc, out, err)): self.failUnlessReallyEqual(rc, 0) results = fileutil.read(fn2) self.failUnlessReallyEqual(results, DATA1) d.addCallback(_copy_file) # Test copying a filecap to local dir, which should fail without a # destination filename (#761). d.addCallback(lambda ign: self.do_cli("cp", self.filecap, outdir)) def _resp((rc, out, err)): self.failUnlessReallyEqual(rc, 1) self.failUnlessIn("when copying into a directory, all source files must have names, but", err) self.failUnlessReallyEqual(out, "") d.addCallback(_resp) # Create a directory, linked at tahoe:test . d.addCallback(lambda ign: self.do_cli("mkdir", "tahoe:test")) def _get_dir((rc, out, err)): self.failUnlessReallyEqual(rc, 0) self.dircap = out.strip() d.addCallback(_get_dir) # Upload a file to the directory. d.addCallback(lambda ign: self.do_cli("put", fn1, "tahoe:test/test_file")) d.addCallback(lambda (rc, out, err): self.failUnlessReallyEqual(rc, 0)) # Copying DIRCAP/filename to a local dir should work, because the # destination filename can be inferred. d.addCallback(lambda ign: self.do_cli("cp", self.dircap + "/test_file", outdir)) def _get_resp((rc, out, err)): self.failUnlessReallyEqual(rc, 0) results = fileutil.read(os.path.join(outdir, "test_file")) self.failUnlessReallyEqual(results, DATA1) d.addCallback(_get_resp) # ... and to an explicit filename different from the source filename. d.addCallback(lambda ign: self.do_cli("cp", self.dircap + "/test_file", fn3)) def _get_resp2((rc, out, err)): self.failUnlessReallyEqual(rc, 0) results = fileutil.read(fn3) self.failUnlessReallyEqual(results, DATA1) d.addCallback(_get_resp2) # Test that the --verbose option prints correct indices (#1805). d.addCallback(lambda ign: self.do_cli("cp", "--verbose", fn3, self.dircap)) def _test_for_wrong_indices((rc, out, err)): lines = err.split('\n') self.failUnlessIn('examining 1 of 1', lines) self.failUnlessIn('starting copy, 1 files, 1 directories', lines) self.failIfIn('examining 0 of', err) d.addCallback(_test_for_wrong_indices) return d def test_cp_with_nonexistent_alias(self): # when invoked with an alias or aliases that don't exist, 'tahoe cp' # should output a sensible error message rather than a stack trace. self.basedir = "cli/Cp/cp_with_nonexistent_alias" self.set_up_grid() d = self.do_cli("cp", "fake:file1", "fake:file2") def _check((rc, out, err)): self.failUnlessReallyEqual(rc, 1) self.failUnlessIn("error:", err) d.addCallback(_check) # 'tahoe cp' actually processes the target argument first, so we need # to check to make sure that validation extends to the source # argument. d.addCallback(lambda ign: self.do_cli("create-alias", "tahoe")) d.addCallback(lambda ign: self.do_cli("cp", "fake:file1", "tahoe:file2")) d.addCallback(_check) return d def test_unicode_dirnames(self): self.basedir = "cli/Cp/unicode_dirnames" fn1 = os.path.join(unicode(self.basedir), u"\u00C4rtonwall") try: fn1_arg = fn1.encode(get_io_encoding()) del fn1_arg # hush pyflakes artonwall_arg = u"\u00C4rtonwall".encode(get_io_encoding()) except UnicodeEncodeError: raise unittest.SkipTest("A non-ASCII command argument could not be encoded on this platform.") self.skip_if_cannot_represent_filename(fn1) self.set_up_grid() d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda res: self.do_cli("mkdir", "tahoe:test/" + artonwall_arg)) d.addCallback(lambda res: self.do_cli("cp", "-r", "tahoe:test", "tahoe:test2")) d.addCallback(lambda res: self.do_cli("ls", "tahoe:test2/test")) def _check((rc, out, err)): try: unicode_to_output(u"\u00C4rtonwall") except UnicodeEncodeError: self.failUnlessReallyEqual(rc, 1) self.failUnlessReallyEqual(out, "") self.failUnlessIn(quote_output(u"\u00C4rtonwall"), err) self.failUnlessIn("files whose names could not be converted", err) else: self.failUnlessReallyEqual(rc, 0) self.failUnlessReallyEqual(out.decode(get_io_encoding()), u"\u00C4rtonwall\n") self.failUnlessReallyEqual(err, "") d.addCallback(_check) return d def test_cp_replaces_mutable_file_contents(self): self.basedir = "cli/Cp/cp_replaces_mutable_file_contents" self.set_up_grid() # Write a test file, which we'll copy to the grid. test_txt_path = os.path.join(self.basedir, "test.txt") test_txt_contents = "foo bar baz" f = open(test_txt_path, "w") f.write(test_txt_contents) f.close() d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda ignored: self.do_cli("mkdir", "tahoe:test")) # We have to use 'tahoe put' here because 'tahoe cp' doesn't # know how to make mutable files at the destination. d.addCallback(lambda ignored: self.do_cli("put", "--mutable", test_txt_path, "tahoe:test/test.txt")) d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test/test.txt")) def _check((rc, out, err)): self.failUnlessEqual(rc, 0) self.failUnlessEqual(out, test_txt_contents) d.addCallback(_check) # We'll do ls --json to get the read uri and write uri for the # file we've just uploaded. d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test/test.txt")) def _get_test_txt_uris((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "filenode") self.failUnless(data['mutable']) self.failUnlessIn("rw_uri", data) self.rw_uri = to_str(data["rw_uri"]) self.failUnlessIn("ro_uri", data) self.ro_uri = to_str(data["ro_uri"]) d.addCallback(_get_test_txt_uris) # Now make a new file to copy in place of test.txt. new_txt_path = os.path.join(self.basedir, "new.txt") new_txt_contents = "baz bar foo" * 100000 f = open(new_txt_path, "w") f.write(new_txt_contents) f.close() # Copy the new file on top of the old file. d.addCallback(lambda ignored: self.do_cli("cp", new_txt_path, "tahoe:test/test.txt")) # If we get test.txt now, we should see the new data. d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test/test.txt")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, new_txt_contents)) # If we get the json of the new file, we should see that the old # uri is there d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test/test.txt")) def _check_json((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "filenode") self.failUnless(data['mutable']) self.failUnlessIn("ro_uri", data) self.failUnlessEqual(to_str(data["ro_uri"]), self.ro_uri) self.failUnlessIn("rw_uri", data) self.failUnlessEqual(to_str(data["rw_uri"]), self.rw_uri) d.addCallback(_check_json) # and, finally, doing a GET directly on one of the old uris # should give us the new contents. d.addCallback(lambda ignored: self.do_cli("get", self.rw_uri)) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, new_txt_contents)) # Now copy the old test.txt without an explicit destination # file. tahoe cp will match it to the existing file and # overwrite it appropriately. d.addCallback(lambda ignored: self.do_cli("cp", test_txt_path, "tahoe:test")) d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test/test.txt")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, test_txt_contents)) d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test/test.txt")) d.addCallback(_check_json) d.addCallback(lambda ignored: self.do_cli("get", self.rw_uri)) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, test_txt_contents)) # Now we'll make a more complicated directory structure. # test2/ # test2/mutable1 # test2/mutable2 # test2/imm1 # test2/imm2 imm_test_txt_path = os.path.join(self.basedir, "imm_test.txt") imm_test_txt_contents = test_txt_contents * 10000 fileutil.write(imm_test_txt_path, imm_test_txt_contents) d.addCallback(lambda ignored: self.do_cli("mkdir", "tahoe:test2")) d.addCallback(lambda ignored: self.do_cli("put", "--mutable", new_txt_path, "tahoe:test2/mutable1")) d.addCallback(lambda ignored: self.do_cli("put", "--mutable", new_txt_path, "tahoe:test2/mutable2")) d.addCallback(lambda ignored: self.do_cli('put', new_txt_path, "tahoe:test2/imm1")) d.addCallback(lambda ignored: self.do_cli("put", imm_test_txt_path, "tahoe:test2/imm2")) d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test2")) def _process_directory_json((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "dirnode") self.failUnless(data['mutable']) self.failUnlessIn("children", data) children = data['children'] # Store the URIs for later use. self.childuris = {} for k in ["mutable1", "mutable2", "imm1", "imm2"]: self.failUnlessIn(k, children) childtype, childdata = children[k] self.failUnlessEqual(childtype, "filenode") if "mutable" in k: self.failUnless(childdata['mutable']) self.failUnlessIn("rw_uri", childdata) uri_key = "rw_uri" else: self.failIf(childdata['mutable']) self.failUnlessIn("ro_uri", childdata) uri_key = "ro_uri" self.childuris[k] = to_str(childdata[uri_key]) d.addCallback(_process_directory_json) # Now build a local directory to copy into place, like the following: # test2/ # test2/mutable1 # test2/mutable2 # test2/imm1 # test2/imm3 def _build_local_directory(ignored): test2_path = os.path.join(self.basedir, "test2") fileutil.make_dirs(test2_path) for fn in ("mutable1", "mutable2", "imm1", "imm3"): fileutil.write(os.path.join(test2_path, fn), fn * 1000) self.test2_path = test2_path d.addCallback(_build_local_directory) d.addCallback(lambda ignored: self.do_cli("cp", "-r", self.test2_path, "tahoe:")) # We expect that mutable1 and mutable2 are overwritten in-place, # so they'll retain their URIs but have different content. def _process_file_json((rc, out, err), fn): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "filenode") if "mutable" in fn: self.failUnless(data['mutable']) self.failUnlessIn("rw_uri", data) self.failUnlessEqual(to_str(data["rw_uri"]), self.childuris[fn]) else: self.failIf(data['mutable']) self.failUnlessIn("ro_uri", data) self.failIfEqual(to_str(data["ro_uri"]), self.childuris[fn]) for fn in ("mutable1", "mutable2"): d.addCallback(lambda ignored, fn=fn: self.do_cli("get", "tahoe:test2/%s" % fn)) d.addCallback(lambda (rc, out, err), fn=fn: self.failUnlessEqual(out, fn * 1000)) d.addCallback(lambda ignored, fn=fn: self.do_cli("ls", "--json", "tahoe:test2/%s" % fn)) d.addCallback(_process_file_json, fn=fn) # imm1 should have been replaced, so both its uri and content # should be different. d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test2/imm1")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, "imm1" * 1000)) d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test2/imm1")) d.addCallback(_process_file_json, fn="imm1") # imm3 should have been created. d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test2/imm3")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, "imm3" * 1000)) # imm2 should be exactly as we left it, since our newly-copied # directory didn't contain an imm2 entry. d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test2/imm2")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, imm_test_txt_contents)) d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test2/imm2")) def _process_imm2_json((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "filenode") self.failIf(data['mutable']) self.failUnlessIn("ro_uri", data) self.failUnlessEqual(to_str(data["ro_uri"]), self.childuris["imm2"]) d.addCallback(_process_imm2_json) return d def test_cp_overwrite_readonly_mutable_file(self): # tahoe cp should print an error when asked to overwrite a # mutable file that it can't overwrite. self.basedir = "cli/Cp/overwrite_readonly_mutable_file" self.set_up_grid() # This is our initial file. We'll link its readcap into the # tahoe: alias. test_file_path = os.path.join(self.basedir, "test_file.txt") test_file_contents = "This is a test file." fileutil.write(test_file_path, test_file_contents) # This is our replacement file. We'll try and fail to upload it # over the readcap that we linked into the tahoe: alias. replacement_file_path = os.path.join(self.basedir, "replacement.txt") replacement_file_contents = "These are new contents." fileutil.write(replacement_file_path, replacement_file_contents) d = self.do_cli("create-alias", "tahoe:") d.addCallback(lambda ignored: self.do_cli("put", "--mutable", test_file_path)) def _get_test_uri((rc, out, err)): self.failUnlessEqual(rc, 0) # this should be a write uri self._test_write_uri = out d.addCallback(_get_test_uri) d.addCallback(lambda ignored: self.do_cli("ls", "--json", self._test_write_uri)) def _process_test_json((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "filenode") self.failUnless(data['mutable']) self.failUnlessIn("ro_uri", data) self._test_read_uri = to_str(data["ro_uri"]) d.addCallback(_process_test_json) # Now we'll link the readonly URI into the tahoe: alias. d.addCallback(lambda ignored: self.do_cli("ln", self._test_read_uri, "tahoe:test_file.txt")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(rc, 0)) # Let's grab the json of that to make sure that we did it right. d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:")) def _process_tahoe_json((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "dirnode") self.failUnlessIn("children", data) kiddata = data['children'] self.failUnlessIn("test_file.txt", kiddata) testtype, testdata = kiddata['test_file.txt'] self.failUnlessEqual(testtype, "filenode") self.failUnless(testdata['mutable']) self.failUnlessIn("ro_uri", testdata) self.failUnlessEqual(to_str(testdata["ro_uri"]), self._test_read_uri) self.failIfIn("rw_uri", testdata) d.addCallback(_process_tahoe_json) # Okay, now we're going to try uploading another mutable file in # place of that one. We should get an error. d.addCallback(lambda ignored: self.do_cli("cp", replacement_file_path, "tahoe:test_file.txt")) def _check_error_message((rc, out, err)): self.failUnlessEqual(rc, 1) self.failUnlessIn("replace or update requested with read-only cap", err) d.addCallback(_check_error_message) # Make extra sure that that didn't work. d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test_file.txt")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, test_file_contents)) d.addCallback(lambda ignored: self.do_cli("get", self._test_read_uri)) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, test_file_contents)) # Now we'll do it without an explicit destination. d.addCallback(lambda ignored: self.do_cli("cp", test_file_path, "tahoe:")) d.addCallback(_check_error_message) d.addCallback(lambda ignored: self.do_cli("get", "tahoe:test_file.txt")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, test_file_contents)) d.addCallback(lambda ignored: self.do_cli("get", self._test_read_uri)) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(out, test_file_contents)) # Now we'll link a readonly file into a subdirectory. d.addCallback(lambda ignored: self.do_cli("mkdir", "tahoe:testdir")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(rc, 0)) d.addCallback(lambda ignored: self.do_cli("ln", self._test_read_uri, "tahoe:test/file2.txt")) d.addCallback(lambda (rc, out, err): self.failUnlessEqual(rc, 0)) test_dir_path = os.path.join(self.basedir, "test") fileutil.make_dirs(test_dir_path) for f in ("file1.txt", "file2.txt"): fileutil.write(os.path.join(test_dir_path, f), f * 10000) d.addCallback(lambda ignored: self.do_cli("cp", "-r", test_dir_path, "tahoe:")) d.addCallback(_check_error_message) d.addCallback(lambda ignored: self.do_cli("ls", "--json", "tahoe:test")) def _got_testdir_json((rc, out, err)): self.failUnlessEqual(rc, 0) filetype, data = simplejson.loads(out) self.failUnlessEqual(filetype, "dirnode") self.failUnlessIn("children", data) childdata = data['children'] self.failUnlessIn("file2.txt", childdata) file2type, file2data = childdata['file2.txt'] self.failUnlessEqual(file2type, "filenode") self.failUnless(file2data['mutable']) self.failUnlessIn("ro_uri", file2data) self.failUnlessEqual(to_str(file2data["ro_uri"]), self._test_read_uri) self.failIfIn("rw_uri", file2data) d.addCallback(_got_testdir_json) return d def test_cp_verbose(self): self.basedir = "cli/Cp/cp_verbose" self.set_up_grid() # Write two test files, which we'll copy to the grid. test1_path = os.path.join(self.basedir, "test1") test2_path = os.path.join(self.basedir, "test2") fileutil.write(test1_path, "test1") fileutil.write(test2_path, "test2") d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda ign: self.do_cli("cp", "--verbose", test1_path, test2_path, "tahoe:")) def _check(res): (rc, out, err) = res self.failUnlessEqual(rc, 0, str(res)) self.failUnlessIn("Success: files copied", out, str(res)) self.failUnlessEqual(err, """\ attaching sources to targets, 2 files / 0 dirs in root targets assigned, 1 dirs, 2 files starting copy, 2 files, 1 directories 1/2 files, 0/1 directories 2/2 files, 0/1 directories 1/1 directories """, str(res)) d.addCallback(_check) return d def test_cp_copies_dir(self): # This test ensures that a directory is copied using # tahoe cp -r. Refer to ticket #712: # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/712 self.basedir = "cli/Cp/cp_copies_dir" self.set_up_grid() subdir = os.path.join(self.basedir, "foo") os.mkdir(subdir) test1_path = os.path.join(subdir, "test1") fileutil.write(test1_path, "test1") d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda ign: self.do_cli("cp", "-r", subdir, "tahoe:")) d.addCallback(lambda ign: self.do_cli("ls", "tahoe:")) def _check(res, item): (rc, out, err) = res self.failUnlessEqual(rc, 0) self.failUnlessEqual(err, "") self.failUnlessIn(item, out, str(res)) d.addCallback(_check, "foo") d.addCallback(lambda ign: self.do_cli("ls", "tahoe:foo/")) d.addCallback(_check, "test1") d.addCallback(lambda ign: fileutil.rm_dir(subdir)) d.addCallback(lambda ign: self.do_cli("cp", "-r", "tahoe:foo", self.basedir)) def _check_local_fs(ign): self.failUnless(os.path.isdir(self.basedir)) self.failUnless(os.path.isfile(test1_path)) d.addCallback(_check_local_fs) return d def test_ticket_2027(self): # This test ensures that tahoe will copy a file from the grid to # a local directory without a specified file name. # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2027 self.basedir = "cli/Cp/cp_verbose" self.set_up_grid() # Write a test file, which we'll copy to the grid. test1_path = os.path.join(self.basedir, "test1") fileutil.write(test1_path, "test1") d = self.do_cli("create-alias", "tahoe") d.addCallback(lambda ign: self.do_cli("cp", test1_path, "tahoe:")) d.addCallback(lambda ign: self.do_cli("cp", "tahoe:test1", self.basedir)) def _check(res): (rc, out, err) = res self.failUnlessIn("Success: file copied", out, str(res)) return d # these test cases come from ticket #2329 comment 40 # trailing slash on target *directory* should not matter, test both # trailing slash on target files should cause error # trailing slash on source directory should not matter, test a few # trailing slash on source files should cause error COPYOUT_TESTCASES = """ cp $FILECAP to/existing-file : to/existing-file cp -r $FILECAP to/existing-file : to/existing-file cp $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file : E6-MANYONE cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file : E6-MANYONE cp $DIRCAP to/existing-file : E4-NEED-R cp -r $DIRCAP to/existing-file : E5-DIRTOFILE cp $FILECAP $DIRCAP to/existing-file : E4-NEED-R cp -r $FILECAP $DIRCAP to/existing-file : E6-MANYONE cp $FILECAP to/existing-file/ : E7-BADSLASH cp -r $FILECAP to/existing-file/ : E7-BADSLASH cp $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file/ : E7-BADSLASH cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/existing-file/ : E7-BADSLASH cp $DIRCAP to/existing-file/ : E4-NEED-R cp -r $DIRCAP to/existing-file/ : E7-BADSLASH cp $FILECAP $DIRCAP to/existing-file/ : E4-NEED-R cp -r $FILECAP $DIRCAP to/existing-file/ : E7-BADSLASH # single source to a (present) target directory cp $FILECAP to : E2-DESTNAME cp -r $FILECAP to : E2-DESTNAME cp $DIRCAP/file to : to/file cp -r $DIRCAP/file to : to/file # these two are errors cp $DIRCAP/file/ to : E8-BADSLASH cp -r $DIRCAP/file/ to : E8-BADSLASH cp $PARENTCAP/dir to : E4-NEED-R cp -r $PARENTCAP/dir to : to/dir/file # but these two should ignore the trailing source slash cp $PARENTCAP/dir/ to : E4-NEED-R cp -r $PARENTCAP/dir/ to : to/dir/file cp $DIRCAP to : E4-NEED-R cp -r $DIRCAP to : to/file cp $DIRALIAS to : E4-NEED-R cp -r $DIRALIAS to : to/file cp $FILECAP to/ : E2-DESTNAME cp -r $FILECAP to/ : E2-DESTNAME cp $DIRCAP/file to/ : to/file cp -r $DIRCAP/file to/ : to/file cp $PARENTCAP/dir to/ : E4-NEED-R cp -r $PARENTCAP/dir to/ : to/dir/file cp $DIRCAP to/ : E4-NEED-R cp -r $DIRCAP to/ : to/file cp $DIRALIAS to/ : E4-NEED-R cp -r $DIRALIAS to/ : to/file # multiple sources to a (present) target directory cp $DIRCAP/file $PARENTCAP/dir2/file2 to : to/file,to/file2 cp $DIRCAP/file $FILECAP to : E2-DESTNAME cp $DIRCAP $FILECAP to : E4-NEED-R cp -r $DIRCAP $FILECAP to : E2-DESTNAME # namedfile, unnameddir, nameddir cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to : to/file3,to/file,to/dir2/file2 # namedfile, unnameddir, nameddir, unnamedfile cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to : E2-DESTNAME cp $DIRCAP/file $PARENTCAP/dir2/file2 to/ : to/file,to/file2 cp $DIRCAP/file $FILECAP to/ : E2-DESTNAME cp $DIRCAP $FILECAP to/ : E4-NEED-R cp -r $DIRCAP $FILECAP to/ : E2-DESTNAME # namedfile, unnameddir, nameddir cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to/ : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to/ : to/file3,to/file,to/dir2/file2 # namedfile, unnameddir, nameddir, unnamedfile cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/ : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/ : E2-DESTNAME # single sources to a missing target: should mkdir or create a file cp $FILECAP to/missing : to/missing cp -r $FILECAP to/missing : to/missing cp $DIRCAP/file to/missing : to/missing cp -r $DIRCAP/file to/missing : to/missing cp $PARENTCAP/dir to/missing : E4-NEED-R cp -r $PARENTCAP/dir to/missing : to/missing/dir/file cp $DIRCAP to/missing : E4-NEED-R cp -r $DIRCAP to/missing : to/missing/file cp $DIRALIAS to/missing : E4-NEED-R cp -r $DIRALIAS to/missing : to/missing/file cp $FILECAP to/missing/ : E7-BADSLASH cp -r $FILECAP to/missing/ : E7-BADSLASH cp $DIRCAP/file to/missing/ : E7-BADSLASH cp -r $DIRCAP/file to/missing/ : E7-BADSLASH cp $PARENTCAP/dir to/missing/ : E4-NEED-R cp -r $PARENTCAP/dir to/missing/ : to/missing/dir/file cp $DIRCAP to/missing/ : E4-NEED-R cp -r $DIRCAP to/missing/ : to/missing/file cp $DIRALIAS to/missing/ : E4-NEED-R cp -r $DIRALIAS to/missing/ : to/missing/file # multiple things to a missing target: should mkdir cp $DIRCAP/file $PARENTCAP/dir2/file2 to/missing : to/missing/file,to/missing/file2 cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/missing : to/missing/file,to/missing/file2 cp $DIRCAP/file $FILECAP to/missing : E2-DESTNAME cp -r $DIRCAP/file $FILECAP to/missing : E2-DESTNAME cp $DIRCAP $FILECAP to/missing : E4-NEED-R cp -r $DIRCAP $FILECAP to/missing : E2-DESTNAME # namedfile, unnameddir, nameddir cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to/missing : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to/missing : to/missing/file3,to/missing/file,to/missing/dir2/file2 # namedfile, unnameddir, nameddir, unnamedfile cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing : E2-DESTNAME cp $DIRCAP/file $PARENTCAP/dir2/file2 to/missing/ : to/missing/file,to/missing/file2 cp -r $DIRCAP/file $PARENTCAP/dir2/file2 to/missing/ : to/missing/file,to/missing/file2 cp $DIRCAP/file $FILECAP to/missing/ : E2-DESTNAME cp -r $DIRCAP/file $FILECAP to/missing/ : E2-DESTNAME cp $DIRCAP $FILECAP to/missing/ : E4-NEED-R cp -r $DIRCAP $FILECAP to/missing/ : E2-DESTNAME # namedfile, unnameddir, nameddir cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to/missing/ : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 to/missing/ : to/missing/file3,to/missing/file,to/missing/dir2/file2 # namedfile, unnameddir, nameddir, unnamedfile cp $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing/ : E4-NEED-R cp -r $PARENTCAP/dir3/file3 $DIRCAP $PARENTCAP/dir2 $FILECAP to/missing/ : E2-DESTNAME # make sure empty directories are copied too cp -r $PARENTCAP/dir4 to : to/dir4/emptydir/ cp -r $PARENTCAP/dir4 to/ : to/dir4/emptydir/ # name collisions should cause errors, not overwrites cp -r $PARENTCAP/dir6/dir $PARENTCAP/dir5/dir to : E9-COLLIDING-TARGETS cp -r $PARENTCAP/dir5/dir $PARENTCAP/dir6/dir to : E9-COLLIDING-TARGETS cp -r $DIRCAP6 $DIRCAP5 to : E9-COLLIDING-TARGETS cp -r $DIRCAP5 $DIRCAP6 to : E9-COLLIDING-TARGETS """ class CopyOut(GridTestMixin, CLITestMixin, unittest.TestCase): FILE_CONTENTS = "file text" FILE_CONTENTS_5 = "5" FILE_CONTENTS_6 = "6" def do_setup(self): # first we build a tahoe filesystem that contains: # $PARENTCAP # $PARENTCAP/dir == $DIRCAP == alias: # $PARENTCAP/dir/file == $FILECAP # $PARENTCAP/dir2 (named directory) # $PARENTCAP/dir2/file2 # $PARENTCAP/dir3/file3 (a second named file) # $PARENTCAP/dir4 # $PARENTCAP/dir4/emptydir/ (an empty directory) # $PARENTCAP/dir5 == $DIRCAP5 # $PARENTCAP/dir5/dir/collide (contents are "5") # $PARENTCAP/dir6 == $DIRCAP6 # $PARENTCAP/dir6/dir/collide (contents are "6") source_file = os.path.join(self.basedir, "file") fileutil.write(source_file, self.FILE_CONTENTS) source_file_5 = os.path.join(self.basedir, "file5") fileutil.write(source_file_5, self.FILE_CONTENTS_5) source_file_6 = os.path.join(self.basedir, "file6") fileutil.write(source_file_6, self.FILE_CONTENTS_6) d = self.do_cli("mkdir") def _stash_parentdircap(res): (rc, out, err) = res self.failUnlessEqual(rc, 0, str(res)) self.failUnlessEqual(err, "", str(res)) self.PARENTCAP = out.strip() return self.do_cli("mkdir", "%s/dir" % self.PARENTCAP) d.addCallback(_stash_parentdircap) def _stash_dircap(res): (rc, out, err) = res self.failUnlessEqual(rc, 0, str(res)) self.failUnlessEqual(err, "", str(res)) self.DIRCAP = out.strip() return self.do_cli("add-alias", "ALIAS", self.DIRCAP) d.addCallback(_stash_dircap) d.addCallback(lambda ign: self.do_cli("put", source_file, "%s/dir/file" % self.PARENTCAP)) def _stash_filecap(res): (rc, out, err) = res self.failUnlessEqual(rc, 0, str(res)) self.failUnlessEqual(err.strip(), "201 Created", str(res)) self.FILECAP = out.strip() assert self.FILECAP.startswith("URI:LIT:") d.addCallback(_stash_filecap) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir2" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("put", source_file, "%s/dir2/file2" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir3" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("put", source_file, "%s/dir3/file3" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir4" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir4/emptydir" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir5" % self.PARENTCAP)) def _stash_dircap_5(res): (rc, out, err) = res self.failUnlessEqual(rc, 0, str(res)) self.failUnlessEqual(err, "", str(res)) self.DIRCAP5 = out.strip() d.addCallback(_stash_dircap_5) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir5/dir" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("put", source_file_5, "%s/dir5/dir/collide" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir6" % self.PARENTCAP)) def _stash_dircap_6(res): (rc, out, err) = res self.failUnlessEqual(rc, 0, str(res)) self.failUnlessEqual(err, "", str(res)) self.DIRCAP6 = out.strip() d.addCallback(_stash_dircap_6) d.addCallback(lambda ign: self.do_cli("mkdir", "%s/dir6/dir" % self.PARENTCAP)) d.addCallback(lambda ign: self.do_cli("put", source_file_6, "%s/dir6/dir/collide" % self.PARENTCAP)) return d def check_output(self): # locate the files and directories created (if any) under to/ top = os.path.join(self.basedir, "to") results = set() for (dirpath, dirnames, filenames) in os.walk(top): assert dirpath.startswith(top) here = "/".join(dirpath.split(os.sep)[len(top.split(os.sep))-1:]) results.add(here+"/") for fn in filenames: contents = fileutil.read(os.path.join(dirpath, fn)) if contents == self.FILE_CONTENTS: results.add("%s/%s" % (here, fn)) elif contents == self.FILE_CONTENTS_5: results.add("%s/%s=5" % (here, fn)) elif contents == self.FILE_CONTENTS_6: results.add("%s/%s=6" % (here, fn)) return results def run_one_case(self, case): cmd = (case .replace("$PARENTCAP", self.PARENTCAP) .replace("$DIRCAP5", self.DIRCAP5) .replace("$DIRCAP6", self.DIRCAP6) .replace("$DIRCAP", self.DIRCAP) .replace("$DIRALIAS", "ALIAS:") .replace("$FILECAP", self.FILECAP) .split()) target = cmd[-1] _assert(target == "to" or target.startswith("to/"), target) cmd[-1] = os.path.abspath(os.path.join(self.basedir, cmd[-1])) # reset targetdir = os.path.abspath(os.path.join(self.basedir, "to")) fileutil.rm_dir(targetdir) os.mkdir(targetdir) if target.rstrip("/") == "to/existing-file": fileutil.write(cmd[-1], "existing file contents\n") # The abspath() for cmd[-1] strips a trailing slash, and we want to # test what happens when it is present. So put it back. if target.endswith("/"): cmd[-1] += "/" d = self.do_cli(*cmd) def _check(res): (rc, out, err) = res err = err.strip() if rc == 0: return self.check_output() if rc == 1: self.failUnlessEqual(out, "", str(res)) if "when copying into a directory, all source files must have names, but" in err: return set(["E2-DESTNAME"]) if err == "cannot copy directories without --recursive": return set(["E4-NEED-R"]) if err == "cannot copy directory into a file": return set(["E5-DIRTOFILE"]) if err == "copying multiple things requires target be a directory": return set(["E6-MANYONE"]) if err == "target is not a directory, but ends with a slash": return set(["E7-BADSLASH"]) if (err.startswith("source ") and "is not a directory, but ends with a slash" in err): return set(["E8-BADSLASH"]) if err == "cannot copy multiple files with the same name into the same target directory": return set(["E9-COLLIDING-TARGETS"]) self.fail("unrecognized error ('%s') %s" % (case, res)) d.addCallback(_check) return d def do_one_test(self, case, orig_expected): expected = set(orig_expected) printable_expected = ",".join(sorted(expected)) #print "---", case, ":", printable_expected for f in orig_expected: # f is "dir/file" or "dir/sub/file" or "dir/" or "dir/sub/" # we want all parent directories in the set, with trailing / pieces = f.rstrip("/").split("/") for i in range(1,len(pieces)): parent = "/".join(pieces[:i]) expected.add(parent+"/") d = self.run_one_case(case) def _dump(got): ok = "ok" if got == expected else "FAIL" printable_got = ",".join(sorted(got)) print "%-31s: got %-19s, want %-19s %s" % (case, printable_got, printable_expected, ok) return got #d.addCallback(_dump) def _check(got): self.failUnlessEqual(got, expected, case) d.addCallback(_check) return d def do_tests(self): # then we run various forms of "cp [-r] TAHOETHING to[/missing]" # and see what happens. d = defer.succeed(None) #print for line in COPYOUT_TESTCASES.splitlines(): if "#" in line: line = line[:line.find("#")] line = line.strip() if not line: continue case, expected = line.split(":") case = case.strip() expected = frozenset(expected.strip().split(",")) d.addCallback(lambda ign, case=case, expected=expected: self.do_one_test(case, expected)) return d def test_cp_out(self): # test copying all sorts of things out of a tahoe filesystem self.basedir = "cli_cp/CopyOut/cp_out" self.set_up_grid(num_servers=1) d = self.do_setup() d.addCallback(lambda ign: self.do_tests()) return d