def test_local_folder_replaced_by_file_and_unsynced_remote_changes(self): # remote folder is currently not checked for unsynced changes but replaced os.mkdir(self.test_folder_local + "/folder") self.wait_for_idle() self.m.pause_sync() self.wait_for_idle() # replace local folder with file delete(self.test_folder_local + "/folder") shutil.copy(self.resources + "/file.txt", self.test_folder_local + "/folder") # create remote changes self.m.client.upload(self.resources + "/file1.txt", self.test_folder_dbx + "/folder/file.txt") self.m.resume_sync() self.wait_for_idle() self.assert_synced(self.test_folder_local, self.test_folder_dbx) self.assert_exists(self.test_folder_dbx, "folder") self.assert_count(self.test_folder_dbx, 1) # check for fatal errors self.assertFalse(self.m.fatal_errors)
def test_folder_tree_local(self): # test creating tree shutil.copytree(self.resources + "/test_folder", self.test_folder_local + "/test_folder") snap = DirectorySnapshot(self.resources + "/test_folder") num_items = len( list(p for p in snap.paths if not self.m.sync.is_excluded(p))) self.wait_for_idle(10) self.assert_synced(self.test_folder_local, self.test_folder_dbx) self.assert_count(self.test_folder_dbx, num_items) # test deleting tree delete(self.test_folder_local + "/test_folder") self.wait_for_idle() self.assert_synced(self.test_folder_local, self.test_folder_dbx) self.assert_count(self.test_folder_dbx, 0) # check for fatal errors self.assertFalse(self.m.fatal_errors)
def test_upload_sync_issues(self): # paths with backslash are not allowed on Dropbox # we create such a local folder and assert that it triggers a sync issue test_path_local = self.test_folder_local + "/folder\\" test_path_dbx = self.test_folder_dbx + "/folder\\" n_errors_initial = len(self.m.sync_errors) os.mkdir(test_path_local) self.wait_for_idle() self.assertEqual(len(self.m.sync_errors), n_errors_initial + 1) self.assertEqual(self.m.sync_errors[-1]["local_path"], test_path_local) self.assertEqual(self.m.sync_errors[-1]["dbx_path"], test_path_dbx) self.assertEqual(self.m.sync_errors[-1]["type"], "PathError") # remove folder with invalid name and assert that sync issue is cleared delete(test_path_local) self.wait_for_idle() self.assertEqual(len(self.m.sync_errors), n_errors_initial) self.assertTrue( all(e["local_path"] != test_path_local for e in self.m.sync_errors)) self.assertTrue( all(e["dbx_path"] != test_path_dbx for e in self.m.sync_errors)) # check for fatal errors self.assertFalse(self.m.fatal_errors)
def test_parallel_deletion_when_paused(self): # create a local file shutil.copy(self.resources + "/file.txt", self.test_folder_local) self.wait_for_idle() self.assert_synced(self.test_folder_local, self.test_folder_dbx) self.m.pause_sync() self.wait_for_idle() # delete local file delete(self.test_folder_local + "/file.txt") # delete remote file self.m.client.remove(self.test_folder_dbx + "/file.txt") self.m.resume_sync() self.wait_for_idle() self.assert_synced(self.test_folder_local, self.test_folder_dbx) self.assert_count(self.test_folder_dbx, 0) # check for fatal errors self.assertFalse(self.m.fatal_errors)
def tearDown(self): self.observer.stop() self.observer.join() remove_configuration("test-config") delete(self.sync.dropbox_path)
def test_unix_permissions(m): """ Tests that a newly downloaded file is created with default permissions for our process and that any locally set permissions are preserved on remote file modifications. """ dbx_path = "/sync_tests/file" local_path = m.to_local_path(dbx_path) m.client.upload(resources + "/file.txt", dbx_path) wait_for_idle(m) # create a local file and compare its permissions to the new download reference_file = osp.join(get_home_dir(), "reference") try: open(reference_file, "ab").close() assert os.stat(local_path).st_mode == os.stat(reference_file).st_mode finally: delete(reference_file) # make the local file executable os.chmod(local_path, 0o744) new_mode = os.stat(local_path).st_mode # might not be 744... wait_for_idle(m) # perform some remote modifications m.client.upload(resources + "/file1.txt", dbx_path, mode=WriteMode.overwrite) wait_for_idle(m) # check that the local permissions have not changed assert os.stat(local_path).st_mode == new_mode
def test_upload_sync_issues(m): """ Tests error handling for issues during upload sync. This is done by creating a local folder with a name that ends with a backslash (not allowed by Dropbox). """ # paths with backslash are not allowed on Dropbox # we create such a local folder and assert that it triggers a sync issue test_path_local = m.test_folder_local + "/folder\\" test_path_dbx = "/sync_tests/folder\\" os.mkdir(test_path_local) wait_for_idle(m) assert len(m.sync_errors) == 1 assert m.sync_errors[-1]["local_path"] == test_path_local assert m.sync_errors[-1]["dbx_path"] == test_path_dbx assert m.sync_errors[-1]["type"] == "PathError" assert test_path_dbx in m.sync.upload_errors # remove folder with invalid name and assert that sync issue is cleared delete(test_path_local) wait_for_idle(m) assert len(m.sync_errors) == 0 assert test_path_dbx not in m.sync.upload_errors # check for fatal errors assert not m.fatal_errors
def test_local_folder_replaced_by_file_and_unsynced_remote_changes(m): """ Tests the upload sync when a local folder is replaced by a file and the remote folder has unsynced changes. """ # remote folder is currently not checked for unsynced changes but replaced os.mkdir(m.test_folder_local + "/folder") wait_for_idle(m) m.pause_sync() wait_for_idle(m) # replace local folder with file delete(m.test_folder_local + "/folder") shutil.copy(resources + "/file.txt", m.test_folder_local + "/folder") # create remote changes m.client.upload(resources + "/file1.txt", "/sync_tests/folder/file.txt") m.resume_sync() wait_for_idle(m) assert_synced(m) assert_exists(m, "/sync_tests", "folder") assert_child_count(m, "/sync_tests", 1) # check for fatal errors assert not m.fatal_errors
def test_folder_tree_created_local(m): """Tests the upload sync of a nested local folder structure.""" # test creating tree shutil.copytree(resources + "/test_folder", m.test_folder_local + "/test_folder") snap = DirectorySnapshot(resources + "/test_folder") num_items = len(list(p for p in snap.paths if not m.sync.is_excluded(p))) wait_for_idle(m, 10) assert_synced(m) assert_child_count(m, "/sync_tests", num_items) # test deleting tree delete(m.test_folder_local + "/test_folder") wait_for_idle(m) assert_synced(m) assert_child_count(m, "/sync_tests", 0) # check for fatal errors assert not m.fatal_errors
def test_parallel_deletion_when_paused(m): """Tests parallel remote and local deletions of an item.""" # create a local file shutil.copy(resources + "/file.txt", m.test_folder_local) wait_for_idle(m) assert_synced(m) m.pause_sync() wait_for_idle(m) # delete local file delete(m.test_folder_local + "/file.txt") # delete remote file m.client.remove("/sync_tests/file.txt") m.resume_sync() wait_for_idle(m) assert_synced(m) assert_child_count(m, "/sync_tests", 0) # check for fatal errors assert not m.fatal_errors
def test_unknown_path_encoding(m, capsys): """ Tests the handling of a local path with bytes that cannot be decoded with the file system encoding reported by the platform. """ # create a path with Python surrogate escapes and convert it to bytes test_path_dbx = "/sync_tests/my_folder_\udce4" test_path_local = m.sync.to_local_path(test_path_dbx) test_path_local_bytes = os.fsencode(test_path_local) # create the local directory while we are syncing os.mkdir(test_path_local_bytes) wait_for_idle(m) # 1) Check that the sync issue is logged # This requires that our "syncing" logic from the emitted watchdog event all the # way to `SyncEngine._on_local_created` can handle strings with surrogate escapes. assert len(m.fatal_errors) == 0 assert len(m.sync_errors) == 1 assert m.sync_errors[-1]["local_path"] == sanitize_string(test_path_local) assert m.sync_errors[-1]["dbx_path"] == sanitize_string(test_path_dbx) assert m.sync_errors[-1]["type"] == "PathError" assert test_path_dbx in m.sync.upload_errors # 2) Check that the sync is retried after pause / resume # This requires that our logic to save failed paths in our state file and retry the # sync on startup can handle strings with surrogate escapes. m.pause_sync() m.resume_sync() wait_for_idle(m) assert len(m.fatal_errors) == 0 assert len(m.sync_errors) == 1 assert m.sync_errors[-1]["local_path"] == sanitize_string(test_path_local) assert m.sync_errors[-1]["dbx_path"] == sanitize_string(test_path_dbx) assert m.sync_errors[-1]["type"] == "PathError" assert test_path_dbx in m.sync.upload_errors # 3) Check that the error is cleared when the file is deleted # This requires that `SyncEngine.upload_local_changes_while_inactive` can handle # strings with surrogate escapes all they way to `SyncEngine._on_local_deleted`. delete(test_path_local_bytes) # type: ignore wait_for_idle(m) assert len(m.fatal_errors) == 0 assert len(m.sync_errors) == 0 assert test_path_dbx not in m.sync.upload_errors
def test_local_folder_replaced_by_file(m): """Tests the upload sync when a local folder is replaced by a file.""" os.mkdir(m.test_folder_local + "/folder") wait_for_idle(m) with m.sync.sync_lock: # replace local folder with file delete(m.test_folder_local + "/folder") shutil.copy(resources + "/file.txt", m.test_folder_local + "/folder") wait_for_idle(m) assert_synced(m) assert osp.isfile(m.test_folder_local + "/folder") assert_child_count(m, "/sync_tests", 1) # check for fatal errors assert not m.fatal_errors
def test_local_folder_replaced_by_file(self): os.mkdir(self.test_folder_local + "/folder") self.wait_for_idle() self.m.pause_sync() # replace local folder with file delete(self.test_folder_local + "/folder") shutil.copy(self.resources + "/file.txt", self.test_folder_local + "/folder") self.m.resume_sync() self.wait_for_idle() self.assert_synced(self.test_folder_local, self.test_folder_dbx) self.assertTrue(osp.isfile(self.test_folder_local + "/folder")) self.assert_count(self.test_folder_dbx, 1) # check for fatal errors self.assertFalse(self.m.fatal_errors)
def sync(): syncing = Event() startup = Event() syncing.set() local_dir = osp.join(get_home_dir(), "dummy_dir") os.mkdir(local_dir) sync = SyncEngine(DropboxClient("test-config"), FSEventHandler(syncing, startup)) sync.dropbox_path = local_dir observer = Observer() observer.schedule(sync.fs_events, sync.dropbox_path, recursive=True) observer.start() yield sync observer.stop() observer.join() remove_configuration("test-config") delete(sync.dropbox_path)
def clean_local(self): """Recreates a fresh test folder locally.""" delete(self.m.dropbox_path + "/.mignore") delete(self.test_folder_local) os.mkdir(self.test_folder_local)