]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/test/test_magic_folder.py
Fix tests usage of umask
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_magic_folder.py
index 32c5c4ec9ee0e44ec3ed171ecff9a040b6abd0fb..4db001f1fddca970bd7005f4ea28819cdbb7e736 100644 (file)
@@ -8,7 +8,6 @@ from allmydata.interfaces import IDirectoryNode
 from allmydata.util.assertutil import precondition
 
 from allmydata.util import fake_inotify, fileutil
-from allmydata.util.deferredutil import DeferredListShouldSucceed
 from allmydata.util.encodingutil import get_filesystem_encoding, to_filepath
 from allmydata.util.consumer import download_to_data
 from allmydata.test.no_network import GridTestMixin
@@ -81,18 +80,19 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
         row = c.fetchone()
         self.failUnlessEqual(row, (pathinfo.size, pathinfo.mtime, pathinfo.ctime))
 
-        # Second test uses db.is_new_file instead of SQL query directly
+        # Second test uses magic_folder.is_new_file instead of SQL query directly
         # to confirm the previous upload entry in the db.
         relpath2 = u"myFile2"
         path2 = os.path.join(self.basedir, relpath2)
         fileutil.write(path2, "meow\n")
         pathinfo = fileutil.get_pathinfo(path2)
         db.did_upload_version(relpath2, 0, 'URI:LIT:2', 'URI:LIT:1', 0, pathinfo)
-        self.failUnlessFalse(db.is_new_file(pathinfo, relpath2))
+        db_entry = db.get_db_entry(relpath2)
+        self.failUnlessFalse(magic_folder.is_new_file(pathinfo, db_entry))
 
         different_pathinfo = fileutil.PathInfo(isdir=False, isfile=True, islink=False,
                                                exists=True, size=0, mtime=pathinfo.mtime, ctime=pathinfo.ctime)
-        self.failUnlessTrue(db.is_new_file(different_pathinfo, relpath2))
+        self.failUnlessTrue(magic_folder.is_new_file(different_pathinfo, db_entry))
 
     def test_magicfolder_start_service(self):
         self.set_up_grid()
@@ -318,7 +318,6 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
         finally:
             yield self.cleanup(None)
 
-
     @defer.inlineCallbacks
     def test_alice_delete_bob_restore(self):
         alice_clock = task.Clock()
@@ -407,6 +406,78 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
             bob_clock.advance(0)
             yield d1
 
+    @defer.inlineCallbacks
+    def test_alice_sees_bobs_delete_with_error(self):
+        # alice creates a file, bob deletes it -- and we also arrange
+        # for Alice's file to have "gone missing" as well.
+        alice_clock = task.Clock()
+        bob_clock = task.Clock()
+        yield self.setup_alice_and_bob(alice_clock, bob_clock)
+        alice_dir = self.alice_magicfolder.uploader._local_path_u
+        bob_dir = self.bob_magicfolder.uploader._local_path_u
+        alice_fname = os.path.join(alice_dir, 'blam')
+        bob_fname = os.path.join(bob_dir, 'blam')
+
+        try:
+            # alice creates a file, bob downloads it
+            alice_proc = self.alice_magicfolder.uploader.set_hook('processed')
+            bob_proc = self.bob_magicfolder.downloader.set_hook('processed')
+
+            fileutil.write(alice_fname, 'contents0\n')
+            self.notify(to_filepath(alice_fname), self.inotify.IN_CLOSE_WRITE, magic=self.alice_magicfolder)
+
+            alice_clock.advance(0)
+            yield alice_proc  # alice uploads
+
+            bob_clock.advance(0)
+            yield bob_proc    # bob downloads
+
+            # check the state
+            yield self._check_version_in_dmd(self.alice_magicfolder, u"blam", 1)
+            yield self._check_version_in_local_db(self.alice_magicfolder, u"blam", 0)
+            yield self._check_version_in_dmd(self.bob_magicfolder, u"blam", 1)
+            yield self._check_version_in_local_db(self.bob_magicfolder, u"blam", 0)
+            yield self.failUnlessReallyEqual(
+                self._get_count('downloader.objects_failed', client=self.bob_magicfolder._client),
+                0
+            )
+            yield self.failUnlessReallyEqual(
+                self._get_count('downloader.objects_downloaded', client=self.bob_magicfolder._client),
+                1
+            )
+
+            # now bob deletes it (bob should upload, alice download)
+            bob_proc = self.bob_magicfolder.uploader.set_hook('processed')
+            alice_proc = self.alice_magicfolder.downloader.set_hook('processed')
+            os.unlink(bob_fname)
+            self.notify(to_filepath(bob_fname), self.inotify.IN_DELETE, magic=self.bob_magicfolder)
+            # just after notifying bob, we also delete alice's,
+            # covering the 'except' flow in _rename_deleted_file()
+            os.unlink(alice_fname)
+
+            bob_clock.advance(0)
+            yield bob_proc
+            alice_clock.advance(0)
+            yield alice_proc
+
+            # check versions
+            node, metadata = yield self.alice_magicfolder.downloader._get_collective_latest_file(u'blam')
+            self.assertTrue(metadata['deleted'])
+            yield self._check_version_in_dmd(self.bob_magicfolder, u"blam", 1)
+            yield self._check_version_in_local_db(self.bob_magicfolder, u"blam", 1)
+            yield self._check_version_in_dmd(self.alice_magicfolder, u"blam", 1)
+            yield self._check_version_in_local_db(self.alice_magicfolder, u"blam", 1)
+
+        finally:
+            # cleanup
+            d0 = self.alice_magicfolder.finish()
+            alice_clock.advance(0)
+            yield d0
+
+            d1 = self.bob_magicfolder.finish()
+            bob_clock.advance(0)
+            yield d1
+
     @defer.inlineCallbacks
     def test_alice_create_bob_update(self):
         alice_clock = task.Clock()
@@ -657,9 +728,10 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
         return d
 
     def _check_version_in_local_db(self, magicfolder, relpath_u, expected_version):
-        version = magicfolder._db.get_local_file_version(relpath_u)
-        #print "_check_version_in_local_db: %r has version %s" % (relpath_u, version)
-        self.failUnlessEqual(version, expected_version)
+        db_entry = magicfolder._db.get_db_entry(relpath_u)
+        if db_entry is not None:
+            #print "_check_version_in_local_db: %r has version %s" % (relpath_u, version)
+            self.failUnlessEqual(db_entry.version, expected_version)
 
     def _check_file_gone(self, magicfolder, relpath_u):
         path = os.path.join(magicfolder.uploader._local_path_u, relpath_u)
@@ -783,7 +855,6 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
 
         def Alice_tries_to_p0wn_Bob(ign):
             print "Alice tries to p0wn Bob\n"
-            self.objects_excluded = self._get_count('downloader.objects_excluded', client=self.bob_magicfolder._client)
             processed_d = self.bob_magicfolder.downloader.set_hook('processed')
 
             # upload a file that would provoke the security bug from #2506
@@ -798,7 +869,6 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
 
         d.addCallback(lambda ign: self.failIf(os.path.exists(path_u)))
         d.addCallback(lambda ign: self._check_version_in_local_db(self.bob_magicfolder, encoded_path_u, None))
-        d.addCallback(lambda ign: self._check_downloader_count('objects_excluded', self.objects_excluded+1))
         d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 3))
         d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
         d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0))
@@ -827,7 +897,7 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
         d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 1, magic=self.alice_magicfolder))
         d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
 
-        def Alice_conflicts_with_Bob():
+        def Alice_conflicts_with_Bobs_last_downloaded_uri():
             print "Alice conflicts with Bob\n"
             downloaded_d = self.bob_magicfolder.downloader.set_hook('processed')
             uploadable = Data("do not follow the white rabbit", self.alice_magicfolder._client.convergence)
@@ -842,11 +912,133 @@ class MagicFolderTestMixin(MagicFolderCLITestMixin, ShouldFailMixin, ReallyEqual
             d2.addCallback(lambda ign: self.failUnless(alice_dmd.has_child(encoded_path_u)))
             return d2
 
-        d.addCallback(lambda ign: Alice_conflicts_with_Bob())
-        # XXX fix the code so that it doesn't increment objects_excluded each turn
-        #d.addCallback(lambda ign: self._check_downloader_count('objects_excluded', 1))
+        d.addCallback(lambda ign: Alice_conflicts_with_Bobs_last_downloaded_uri())
         d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 4))
         d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 1))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 1, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+
+        # prepare to perform another conflict test
+        def Alice_to_write_file2():
+            print "Alice writes a file\n"
+            self.file_path = abspath_expanduser_unicode(u"file2", base=self.alice_magicfolder.uploader._local_path_u)
+            fileutil.write(self.file_path, "something")
+            self.notify(to_filepath(self.file_path), self.inotify.IN_CLOSE_WRITE, magic=self.alice_magicfolder)
+        d.addCallback(_wait_for, Alice_to_write_file2)
+        d.addCallback(lambda ign: self._check_version_in_dmd(self.alice_magicfolder, u"file2", 0))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+
+        def Bob_to_rewrite_file2():
+            print "Bob rewrites file\n"
+            self.file_path = abspath_expanduser_unicode(u"file2", base=self.bob_magicfolder.uploader._local_path_u)
+            print "---- bob's file is %r" % (self.file_path,)
+            fileutil.write(self.file_path, "roger roger. what vector?")
+            self.magicfolder = self.bob_magicfolder
+            self.notify(to_filepath(self.file_path), self.inotify.IN_CLOSE_WRITE)
+        d.addCallback(lambda ign: _wait_for(None, Bob_to_rewrite_file2, alice=False))
+        d.addCallback(lambda ign: self._check_version_in_dmd(self.bob_magicfolder, u"file2", 1))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 5))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 1))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_failed', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_succeeded', 3, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('files_uploaded', 2, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_queued', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('directories_created', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 2, magic=self.alice_magicfolder))
+
+        # XXX here we advance the clock and then test again to make sure no values are monotonically increasing
+        # with each queue turn ;-p
+        alice_clock.advance(6)
+        bob_clock.advance(6)
+        d.addCallback(lambda ign: self._check_version_in_dmd(self.bob_magicfolder, u"file2", 1))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 5))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 1))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_failed', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_succeeded', 3, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('files_uploaded', 2, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_queued', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('directories_created', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 2, magic=self.alice_magicfolder))
+
+        def Alice_conflicts_with_Bobs_last_uploaded_uri():
+            print "Alice conflicts with Bob\n"
+            encoded_path_u = magicpath.path2magic(u"file2")
+            downloaded_d = self.bob_magicfolder.downloader.set_hook('processed')
+            uploadable = Data("rabbits with sharp fangs", self.alice_magicfolder._client.convergence)
+            alice_dmd = self.alice_magicfolder.uploader._upload_dirnode
+            d2 = alice_dmd.add_file(u"file2", uploadable,
+                                    metadata={"version": 5,
+                                              "last_uploaded_uri" : "URI:LIT:" },
+                                    overwrite=True)
+            print "Waiting for Alice to upload\n"
+            d2.addCallback(lambda ign: bob_clock.advance(6))
+            d2.addCallback(lambda ign: downloaded_d)
+            d2.addCallback(lambda ign: self.failUnless(alice_dmd.has_child(encoded_path_u)))
+            return d2
+        d.addCallback(lambda ign: Alice_conflicts_with_Bobs_last_uploaded_uri())
+        d.addCallback(lambda ign: self._check_version_in_dmd(self.bob_magicfolder, u"file2", 5))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 6))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 2))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_failed', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_succeeded', 3, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('files_uploaded', 2, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_queued', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('directories_created', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 2, magic=self.alice_magicfolder))
+
+        alice_clock.advance(6)
+        bob_clock.advance(6)
+        alice_clock.advance(6)
+        bob_clock.advance(6)
+
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 2, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 2))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 6))
+
+        # prepare to perform another conflict test
+        def Alice_to_write_file3():
+            print "Alice writes a file\n"
+            self.file_path = abspath_expanduser_unicode(u"file3", base=self.alice_magicfolder.uploader._local_path_u)
+            fileutil.write(self.file_path, "something")
+            self.notify(to_filepath(self.file_path), self.inotify.IN_CLOSE_WRITE, magic=self.alice_magicfolder)
+        d.addCallback(_wait_for, Alice_to_write_file3)
+        d.addCallback(lambda ign: self._check_version_in_dmd(self.alice_magicfolder, u"file3", 0))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 7))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 2, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 2))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+
+        def Bob_to_rewrite_file3():
+            print "Bob rewrites file\n"
+            self.file_path = abspath_expanduser_unicode(u"file3", base=self.bob_magicfolder.uploader._local_path_u)
+            print "---- bob's file is %r" % (self.file_path,)
+            fileutil.write(self.file_path, "roger roger")
+            self.magicfolder = self.bob_magicfolder
+            self.notify(to_filepath(self.file_path), self.inotify.IN_CLOSE_WRITE)
+        d.addCallback(lambda ign: _wait_for(None, Bob_to_rewrite_file3, alice=False))
+        d.addCallback(lambda ign: self._check_version_in_dmd(self.bob_magicfolder, u"file3", 1))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 7))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 2))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_failed', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_succeeded', 4, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('files_uploaded', 3, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('objects_queued', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_uploader_count('directories_created', 0, magic=self.bob_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_conflicted', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_failed', 0, magic=self.alice_magicfolder))
+        d.addCallback(lambda ign: self._check_downloader_count('objects_downloaded', 3, magic=self.alice_magicfolder))
+
+
 
         def _cleanup(ign, magicfolder, clock):
             if magicfolder is not None:
@@ -897,24 +1089,23 @@ class MockTest(MagicFolderTestMixin, unittest.TestCase):
             readonly_dircap = n.get_readonly_uri()
 
             self.shouldFail(AssertionError, 'nonexistent local.directory', 'there is no directory',
-                            MagicFolder, client, upload_dircap, '', doesnotexist, magicfolderdb)
+                            MagicFolder, client, upload_dircap, '', doesnotexist, magicfolderdb, 0077)
             self.shouldFail(AssertionError, 'non-directory local.directory', 'is not a directory',
-                            MagicFolder, client, upload_dircap, '', not_a_dir, magicfolderdb)
+                            MagicFolder, client, upload_dircap, '', not_a_dir, magicfolderdb, 0077)
             self.shouldFail(AssertionError, 'bad upload.dircap', 'does not refer to a directory',
-                            MagicFolder, client, 'bad', '', errors_dir, magicfolderdb)
+                            MagicFolder, client, 'bad', '', errors_dir, magicfolderdb, 0077)
             self.shouldFail(AssertionError, 'non-directory upload.dircap', 'does not refer to a directory',
-                            MagicFolder, client, 'URI:LIT:foo', '', errors_dir, magicfolderdb)
+                            MagicFolder, client, 'URI:LIT:foo', '', errors_dir, magicfolderdb, 0077)
             self.shouldFail(AssertionError, 'readonly upload.dircap', 'is not a writecap to a directory',
-                            MagicFolder, client, readonly_dircap, '', errors_dir, magicfolderdb,)
-            self.shouldFail(AssertionError, 'collective dircap',
-                            "The URI in '%s' is not a readonly cap to a directory." % os.path.join('private', 'collective_dircap'),
-                            MagicFolder, client, upload_dircap, upload_dircap, errors_dir, magicfolderdb)
+                            MagicFolder, client, readonly_dircap, '', errors_dir, magicfolderdb, 0077)
+            self.shouldFail(AssertionError, 'collective dircap', 'is not a readonly cap to a directory',
+                            MagicFolder, client, upload_dircap, upload_dircap, errors_dir, magicfolderdb, 0077)
 
             def _not_implemented():
                 raise NotImplementedError("blah")
             self.patch(magic_folder, 'get_inotify_module', _not_implemented)
             self.shouldFail(NotImplementedError, 'unsupported', 'blah',
-                            MagicFolder, client, upload_dircap, '', errors_dir, magicfolderdb)
+                            MagicFolder, client, upload_dircap, '', errors_dir, magicfolderdb, 0077)
         d.addCallback(_check_errors)
         return d
 
@@ -927,6 +1118,7 @@ class MockTest(MagicFolderTestMixin, unittest.TestCase):
                 pass
 
         writefile = TestWriteFileMixin()
+        writefile._umask = 0077
 
         # create a file with name "foobar" with content "foo"
         # write downloaded file content "bar" into "foobar" with is_conflict = False