]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/test/test_cli.py
Improve 'tahoe put --help' to clarify behaviour for mutable files, and
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_cli.py
index 485414b8cb192756889e4c0a6d2daaa5fea7ab74..d4c26de838afaedda5fd230fdef87844a69a6955 100644 (file)
@@ -2,10 +2,10 @@
 import os.path
 from twisted.trial import unittest
 from cStringIO import StringIO
-import urllib, re
+import urllib, re, sys
 import simplejson
 
-from mock import patch
+from mock import patch, Mock, call
 
 from allmydata.util import fileutil, hashutil, base32, keyutil
 from allmydata import uri
@@ -13,6 +13,8 @@ from allmydata.immutable import upload
 from allmydata.interfaces import MDMF_VERSION, SDMF_VERSION
 from allmydata.mutable.publish import MutableData
 from allmydata.dirnode import normalize
+from allmydata.scripts.common_http import socket_error
+import allmydata.scripts.common_http
 from pycryptopp.publickey import ed25519
 
 # Test that the scripts can be imported.
@@ -580,6 +582,27 @@ class CLI(CLITestMixin, unittest.TestCase):
         for file in listdir_unicode(unicode(basedir)):
             self.failUnlessIn(normalize(file), filenames)
 
+    def test_exception_catcher(self):
+        self.basedir = "cli/exception_catcher"
+
+        runner_mock = Mock()
+        sys_exit_mock = Mock()
+        stderr = StringIO()
+        self.patch(sys, "argv", ["tahoe"])
+        self.patch(runner, "runner", runner_mock)
+        self.patch(sys, "exit", sys_exit_mock)
+        self.patch(sys, "stderr", stderr)
+        exc = Exception("canary")
+
+        def call_runner(args, install_node_control=True):
+            raise exc
+        runner_mock.side_effect = call_runner
+
+        runner.run()
+        self.failUnlessEqual(runner_mock.call_args_list, [call([], install_node_control=True)])
+        self.failUnlessEqual(sys_exit_mock.call_args_list, [call(1)])
+        self.failUnlessIn(str(exc), stderr.getvalue())
+
 
 class Help(unittest.TestCase):
     def test_get(self):
@@ -592,6 +615,10 @@ class Help(unittest.TestCase):
         self.failUnlessIn(" put [options] LOCAL_FILE REMOTE_FILE", help)
         self.failUnlessIn("% cat FILE | tahoe put", help)
 
+    def test_ls(self):
+        help = str(cli.ListOptions())
+        self.failUnlessIn(" ls [options] [PATH]", help)
+
     def test_unlink(self):
         help = str(cli.UnlinkOptions())
         self.failUnlessIn(" unlink [options] REMOTE_FILE", help)
@@ -1146,8 +1173,20 @@ class Put(GridTestMixin, CLITestMixin, unittest.TestCase):
         d = self.do_cli("create-alias", "tahoe")
         d.addCallback(lambda res:
                       self.do_cli("put", "--mutable", fn1, "tahoe:uploaded.txt"))
+        def _check(res):
+            (rc, out, err) = res
+            self.failUnlessEqual(rc, 0, str(res))
+            self.failUnlessEqual(err, "", str(res))
+            self.uri = out
+        d.addCallback(_check)
         d.addCallback(lambda res:
                       self.do_cli("put", fn2, "tahoe:uploaded.txt"))
+        def _check2(res):
+            (rc, out, err) = res
+            self.failUnlessEqual(rc, 0, str(res))
+            self.failUnlessEqual(err, "", str(res))
+            self.failUnlessEqual(out, self.uri, str(res))
+        d.addCallback(_check2)
         d.addCallback(lambda res:
                       self.do_cli("get", "tahoe:uploaded.txt"))
         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
@@ -2029,6 +2068,12 @@ class Cp(GridTestMixin, CLITestMixin, unittest.TestCase):
             results = fileutil.read(fn3)
             self.failUnlessReallyEqual(results, DATA1)
         d.addCallback(_get_resp2)
+        #  cp --verbose filename3 dircap:test_file
+        d.addCallback(lambda ign:
+                      self.do_cli("cp", "--verbose", '--recursive', self.basedir, self.dircap))
+        def _test_for_wrong_indices((rc, out, err)):
+            self.failUnless('examining 1 of 1\n' in err)
+        d.addCallback(_test_for_wrong_indices)
         return d
 
     def test_cp_with_nonexistent_alias(self):
@@ -2435,6 +2480,34 @@ class Cp(GridTestMixin, CLITestMixin, unittest.TestCase):
         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
+
 
 class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
 
@@ -2461,8 +2534,9 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
         # is the backupdb available? If so, we test that a second backup does
         # not create new directories.
         hush = StringIO()
-        have_bdb = backupdb.get_backupdb(os.path.join(self.basedir, "dbtest"),
-                                         hush)
+        bdb = backupdb.get_backupdb(os.path.join(self.basedir, "dbtest"),
+                                    hush)
+        self.failUnless(bdb)
 
         # create a small local directory with a couple of files
         source = os.path.join(self.basedir, "home")
@@ -2481,13 +2555,6 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
 
         d = self.do_cli("create-alias", "tahoe")
 
-        if not have_bdb:
-            d.addCallback(lambda res: self.do_cli("backup", source, "tahoe:backups"))
-            def _should_complain((rc, out, err)):
-                self.failUnless("I was unable to import a python sqlite library" in err, err)
-            d.addCallback(_should_complain)
-            d.addCallback(self.stall, 1.1) # make sure the backups get distinct timestamps
-
         d.addCallback(lambda res: do_backup())
         def _check0((rc, out, err)):
             self.failUnlessReallyEqual(err, "")
@@ -2548,61 +2615,56 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
             # available
             self.failUnlessReallyEqual(err, "")
             self.failUnlessReallyEqual(rc, 0)
-            if have_bdb:
-                fu, fr, fs, dc, dr, ds = self.count_output(out)
-                # foo.txt, bar.txt, blah.txt
-                self.failUnlessReallyEqual(fu, 0)
-                self.failUnlessReallyEqual(fr, 3)
-                self.failUnlessReallyEqual(fs, 0)
-                # empty, home, home/parent, home/parent/subdir
-                self.failUnlessReallyEqual(dc, 0)
-                self.failUnlessReallyEqual(dr, 4)
-                self.failUnlessReallyEqual(ds, 0)
+            fu, fr, fs, dc, dr, ds = self.count_output(out)
+            # foo.txt, bar.txt, blah.txt
+            self.failUnlessReallyEqual(fu, 0)
+            self.failUnlessReallyEqual(fr, 3)
+            self.failUnlessReallyEqual(fs, 0)
+            # empty, home, home/parent, home/parent/subdir
+            self.failUnlessReallyEqual(dc, 0)
+            self.failUnlessReallyEqual(dr, 4)
+            self.failUnlessReallyEqual(ds, 0)
         d.addCallback(_check4a)
 
-        if have_bdb:
-            # sneak into the backupdb, crank back the "last checked"
-            # timestamp to force a check on all files
-            def _reset_last_checked(res):
-                dbfile = os.path.join(self.get_clientdir(),
-                                      "private", "backupdb.sqlite")
-                self.failUnless(os.path.exists(dbfile), dbfile)
-                bdb = backupdb.get_backupdb(dbfile)
-                bdb.cursor.execute("UPDATE last_upload SET last_checked=0")
-                bdb.cursor.execute("UPDATE directories SET last_checked=0")
-                bdb.connection.commit()
-
-            d.addCallback(_reset_last_checked)
-
-            d.addCallback(self.stall, 1.1)
-            d.addCallback(lambda res: do_backup(verbose=True))
-            def _check4b((rc, out, err)):
-                # we should check all files, and re-use all of them. None of
-                # the directories should have been changed, so we should
-                # re-use all of them too.
-                self.failUnlessReallyEqual(err, "")
-                self.failUnlessReallyEqual(rc, 0)
-                fu, fr, fs, dc, dr, ds = self.count_output(out)
-                fchecked, dchecked = self.count_output2(out)
-                self.failUnlessReallyEqual(fchecked, 3)
-                self.failUnlessReallyEqual(fu, 0)
-                self.failUnlessReallyEqual(fr, 3)
-                self.failUnlessReallyEqual(fs, 0)
-                self.failUnlessReallyEqual(dchecked, 4)
-                self.failUnlessReallyEqual(dc, 0)
-                self.failUnlessReallyEqual(dr, 4)
-                self.failUnlessReallyEqual(ds, 0)
-            d.addCallback(_check4b)
+        # sneak into the backupdb, crank back the "last checked"
+        # timestamp to force a check on all files
+        def _reset_last_checked(res):
+            dbfile = os.path.join(self.get_clientdir(),
+                                  "private", "backupdb.sqlite")
+            self.failUnless(os.path.exists(dbfile), dbfile)
+            bdb = backupdb.get_backupdb(dbfile)
+            bdb.cursor.execute("UPDATE last_upload SET last_checked=0")
+            bdb.cursor.execute("UPDATE directories SET last_checked=0")
+            bdb.connection.commit()
+
+        d.addCallback(_reset_last_checked)
+
+        d.addCallback(self.stall, 1.1)
+        d.addCallback(lambda res: do_backup(verbose=True))
+        def _check4b((rc, out, err)):
+            # we should check all files, and re-use all of them. None of
+            # the directories should have been changed, so we should
+            # re-use all of them too.
+            self.failUnlessReallyEqual(err, "")
+            self.failUnlessReallyEqual(rc, 0)
+            fu, fr, fs, dc, dr, ds = self.count_output(out)
+            fchecked, dchecked = self.count_output2(out)
+            self.failUnlessReallyEqual(fchecked, 3)
+            self.failUnlessReallyEqual(fu, 0)
+            self.failUnlessReallyEqual(fr, 3)
+            self.failUnlessReallyEqual(fs, 0)
+            self.failUnlessReallyEqual(dchecked, 4)
+            self.failUnlessReallyEqual(dc, 0)
+            self.failUnlessReallyEqual(dr, 4)
+            self.failUnlessReallyEqual(ds, 0)
+        d.addCallback(_check4b)
 
         d.addCallback(lambda res: self.do_cli("ls", "tahoe:backups/Archives"))
         def _check5((rc, out, err)):
             self.failUnlessReallyEqual(err, "")
             self.failUnlessReallyEqual(rc, 0)
             self.new_archives = out.split()
-            expected_new = 2
-            if have_bdb:
-                expected_new += 1
-            self.failUnlessReallyEqual(len(self.new_archives), expected_new, out)
+            self.failUnlessReallyEqual(len(self.new_archives), 3, out)
             # the original backup should still be the oldest (i.e. sorts
             # alphabetically towards the beginning)
             self.failUnlessReallyEqual(sorted(self.new_archives)[0],
@@ -2627,27 +2689,23 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase):
             # and upload the rest. None of the directories can be reused.
             self.failUnlessReallyEqual(err, "")
             self.failUnlessReallyEqual(rc, 0)
-            if have_bdb:
-                fu, fr, fs, dc, dr, ds = self.count_output(out)
-                # new foo.txt, surprise file, subfile, empty
-                self.failUnlessReallyEqual(fu, 4)
-                # old bar.txt
-                self.failUnlessReallyEqual(fr, 1)
-                self.failUnlessReallyEqual(fs, 0)
-                # home, parent, subdir, blah.txt, surprisedir
-                self.failUnlessReallyEqual(dc, 5)
-                self.failUnlessReallyEqual(dr, 0)
-                self.failUnlessReallyEqual(ds, 0)
+            fu, fr, fs, dc, dr, ds = self.count_output(out)
+            # new foo.txt, surprise file, subfile, empty
+            self.failUnlessReallyEqual(fu, 4)
+            # old bar.txt
+            self.failUnlessReallyEqual(fr, 1)
+            self.failUnlessReallyEqual(fs, 0)
+            # home, parent, subdir, blah.txt, surprisedir
+            self.failUnlessReallyEqual(dc, 5)
+            self.failUnlessReallyEqual(dr, 0)
+            self.failUnlessReallyEqual(ds, 0)
         d.addCallback(_check5a)
         d.addCallback(lambda res: self.do_cli("ls", "tahoe:backups/Archives"))
         def _check6((rc, out, err)):
             self.failUnlessReallyEqual(err, "")
             self.failUnlessReallyEqual(rc, 0)
             self.new_archives = out.split()
-            expected_new = 3
-            if have_bdb:
-                expected_new += 1
-            self.failUnlessReallyEqual(len(self.new_archives), expected_new)
+            self.failUnlessReallyEqual(len(self.new_archives), 4)
             self.failUnlessReallyEqual(sorted(self.new_archives)[0],
                                  self.old_archives[0])
         d.addCallback(_check6)
@@ -2956,8 +3014,41 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
             self.failUnlessReallyEqual(rc, 0)
             data = simplejson.loads(out)
             self.failUnlessReallyEqual(to_str(data["summary"]), "Healthy")
+            self.failUnlessReallyEqual(data["results"]["healthy"], True)
         d.addCallback(_check2)
 
+        d.addCallback(lambda ign: c0.upload(upload.Data("literal", convergence="")))
+        def _stash_lit_uri(n):
+            self.lit_uri = n.get_uri()
+        d.addCallback(_stash_lit_uri)
+
+        d.addCallback(lambda ign: self.do_cli("check", self.lit_uri))
+        def _check_lit((rc, out, err)):
+            self.failUnlessReallyEqual(err, "")
+            self.failUnlessReallyEqual(rc, 0)
+            lines = out.splitlines()
+            self.failUnless("Summary: Healthy (LIT)" in lines, out)
+        d.addCallback(_check_lit)
+
+        d.addCallback(lambda ign: self.do_cli("check", "--raw", self.lit_uri))
+        def _check_lit_raw((rc, out, err)):
+            self.failUnlessReallyEqual(err, "")
+            self.failUnlessReallyEqual(rc, 0)
+            data = simplejson.loads(out)
+            self.failUnlessReallyEqual(data["results"]["healthy"], True)
+        d.addCallback(_check_lit_raw)
+
+        d.addCallback(lambda ign: c0.create_immutable_dirnode({}, convergence=""))
+        def _stash_lit_dir_uri(n):
+            self.lit_dir_uri = n.get_uri()
+        d.addCallback(_stash_lit_dir_uri)
+
+        d.addCallback(lambda ign: self.do_cli("check", self.lit_dir_uri))
+        d.addCallback(_check_lit)
+
+        d.addCallback(lambda ign: self.do_cli("check", "--raw", self.lit_uri))
+        d.addCallback(_check_lit_raw)
+
         def _clobber_shares(ignored):
             # delete one, corrupt a second
             shares = self.find_uri_shares(self.uri)
@@ -2987,6 +3078,18 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
             self.failUnless(self._corrupt_share_line in lines, out)
         d.addCallback(_check3)
 
+        d.addCallback(lambda ign: self.do_cli("check", "--verify", "--raw", self.uri))
+        def _check3_raw((rc, out, err)):
+            self.failUnlessReallyEqual(err, "")
+            self.failUnlessReallyEqual(rc, 0)
+            data = simplejson.loads(out)
+            self.failUnlessReallyEqual(data["results"]["healthy"], False)
+            self.failUnlessIn("Unhealthy: 8 shares (enc 3-of-10)", data["summary"])
+            self.failUnlessReallyEqual(data["results"]["count-shares-good"], 8)
+            self.failUnlessReallyEqual(data["results"]["count-corrupt-shares"], 1)
+            self.failUnlessIn("list-corrupt-shares", data["results"])
+        d.addCallback(_check3_raw)
+
         d.addCallback(lambda ign:
                       self.do_cli("check", "--verify", "--repair", self.uri))
         def _check4((rc, out, err)):
@@ -3262,8 +3365,8 @@ class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
         DATA = "data" * 100
         d = c0.upload(upload.Data(DATA, convergence=""))
         def _stash_bad(ur):
-            self.uri_1share = ur.uri
-            self.delete_shares_numbered(ur.uri, range(1,10))
+            self.uri_1share = ur.get_uri()
+            self.delete_shares_numbered(ur.get_uri(), range(1,10))
         d.addCallback(_stash_bad)
 
         # the download is abandoned as soon as it's clear that we won't get
@@ -3294,6 +3397,25 @@ class Errors(GridTestMixin, CLITestMixin, unittest.TestCase):
 
         return d
 
+    def test_broken_socket(self):
+        # When the http connection breaks (such as when node.url is overwritten
+        # by a confused user), a user friendly error message should be printed.
+        self.basedir = "cli/Errors/test_broken_socket"
+        self.set_up_grid()
+
+        # Simulate a connection error
+        def _socket_error(*args, **kwargs):
+            raise socket_error('test error')
+        self.patch(allmydata.scripts.common_http.httplib.HTTPConnection,
+                   "endheaders", _socket_error)
+
+        d = self.do_cli("mkdir")
+        def _check_invalid((rc,stdout,stderr)):
+            self.failIfEqual(rc, 0)
+            self.failUnlessIn("Error trying to connect to http://127.0.0.1", stderr)
+        d.addCallback(_check_invalid)
+        return d
+
 
 class Get(GridTestMixin, CLITestMixin, unittest.TestCase):
     def test_get_without_alias(self):