package co.codewizards.cloudstore.local.sync; import static co.codewizards.cloudstore.core.io.StreamUtil.*; import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.oio.File; import co.codewizards.cloudstore.core.progress.LoggerProgressMonitor; import co.codewizards.cloudstore.core.progress.NullProgressMonitor; import co.codewizards.cloudstore.core.progress.ProgressMonitor; import co.codewizards.cloudstore.core.repo.local.LocalRepoManager; import co.codewizards.cloudstore.core.util.IOUtil; import co.codewizards.cloudstore.local.AbstractTest; public class RepoToRepoSyncTest extends AbstractTest { private static Logger logger = LoggerFactory.getLogger(RepoToRepoSyncTest.class); private File localRoot; private File remoteRoot; private String localPathPrefix; private String remotePathPrefix; @Override @Before public void before() { localPathPrefix = ""; remotePathPrefix = ""; } private File getLocalRootWithPathPrefix() { if (localPathPrefix.isEmpty()) return localRoot; return createFile(localRoot, localPathPrefix); } private File getRemoteRootWithPathPrefix() { if (remotePathPrefix.isEmpty()) return remoteRoot; final File file = createFile(remoteRoot, remotePathPrefix); return file; } private URL getRemoteRootUrlWithPathPrefix() { try { final URL url = getRemoteRootWithPathPrefix().toURI().toURL(); return url; } catch (final MalformedURLException e) { throw new RuntimeException(e); } } @Test public void syncFromRemoteToLocal() throws Exception { localRoot = newTestRepositoryLocalRoot("local"); assertThat(localRoot.exists()).isFalse(); localRoot.mkdirs(); assertThat(localRoot.isDirectory()).isTrue(); remoteRoot = newTestRepositoryLocalRoot("remote"); assertThat(remoteRoot.exists()).isFalse(); remoteRoot.mkdirs(); assertThat(remoteRoot.isDirectory()).isTrue(); final LocalRepoManager localRepoManagerLocal = localRepoManagerFactory.createLocalRepoManagerForNewRepository(localRoot); assertThat(localRepoManagerLocal).isNotNull(); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForNewRepository(remoteRoot); assertThat(localRepoManagerRemote).isNotNull(); localRepoManagerLocal.putRemoteRepository(localRepoManagerRemote.getRepositoryId(), getRemoteRootUrlWithPathPrefix(), localRepoManagerRemote.getPublicKey(), localPathPrefix); localRepoManagerRemote.putRemoteRepository(localRepoManagerLocal.getRepositoryId(), null, localRepoManagerLocal.getPublicKey(), remotePathPrefix); final File child_1 = createDirectory(remoteRoot, "1"); createFileWithRandomContent(child_1, "a"); createFileWithRandomContent(child_1, "b"); createFileWithRandomContent(child_1, "c"); final File child_2 = createDirectory(remoteRoot, "2"); createFileWithRandomContent(child_2, "a"); final File child_2_1 = createDirectory(child_2, "1"); createFileWithRandomContent(child_2_1, "a"); createFileWithRandomContent(child_2_1, "b", 150000); final File child_3 = createDirectory(remoteRoot, "3"); createFileWithRandomContent(child_3, "a"); createFileWithRandomContent(child_3, "b"); createFileWithRandomContent(child_3, "c"); createFileWithRandomContent(child_3, "d"); localRepoManagerRemote.localSync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); logger.info("local repo: {}", localRepoManagerLocal.getRepositoryId()); logger.info("remote repo: {}", localRepoManagerRemote.getRepositoryId()); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThatFilesInRepoAreCorrect(remoteRoot); localRepoManagerLocal.close(); localRepoManagerRemote.close(); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } @Test public void syncFromRemoteToLocalWithAddedFilesAndDirectories() throws Exception { syncFromRemoteToLocal(); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForExistingRepository(remoteRoot); assertThat(localRepoManagerRemote).isNotNull(); final File child_2 = createFile(remoteRoot, "2"); assertThat(child_2.isDirectory()).isTrue(); final File child_2_1 = createFile(child_2, "1"); assertThat(child_2_1.isDirectory()).isTrue(); final File child_2_1_5 = createDirectory(child_2_1, "5"); createFileWithRandomContent(child_2_1_5, "aaa"); createFileWithRandomContent(child_2_1_5, "bbb"); final File child_3 = createFile(remoteRoot, "3"); assertThat(child_3.isDirectory()).isTrue(); createFileWithRandomContent(child_3, "e"); localRepoManagerRemote.localSync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThatFilesInRepoAreCorrect(remoteRoot); localRepoManagerRemote.close(); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } @Test public void syncFromRemoteToLocalWithModifiedFiles() throws Exception { syncFromRemoteToLocal(); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForExistingRepository(remoteRoot); assertThat(localRepoManagerRemote).isNotNull(); final File child_2 = createFile(remoteRoot, "2"); assertThat(child_2.isDirectory()).isTrue(); final File child_2_1 = createFile(child_2, "1"); assertThat(child_2_1.isDirectory()).isTrue(); final File child_2_1_a = createFile(child_2_1, "a"); assertThat(child_2_1_a.isFile()).isTrue(); final File child_2_1_b = createFile(child_2_1, "b"); assertThat(child_2_1_b.isFile()).isTrue(); modifyFileRandomly(child_2_1_a); logger.info("file='{}' length={}", child_2_1_b, child_2_1_b.length()); final OutputStream out = castStream(child_2_1_b.createOutputStream()); out.write(random.nextInt()); out.close(); logger.info("file='{}' length={}", child_2_1_b, child_2_1_b.length()); final byte[] child_2_1_a_expected = IOUtil.getBytesFromFile(child_2_1_a); final byte[] child_2_1_b_expected = IOUtil.getBytesFromFile(child_2_1_b); localRepoManagerRemote.localSync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThatFilesInRepoAreCorrect(remoteRoot); localRepoManagerRemote.close(); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); // ensure that nothing was synced backwards into the wrong direction ;-) final byte[] child_2_1_a_actual = IOUtil.getBytesFromFile(child_2_1_a); final byte[] child_2_1_b_actual = IOUtil.getBytesFromFile(child_2_1_b); assertThat(child_2_1_a_actual).isEqualTo(child_2_1_a_expected); assertThat(child_2_1_b_actual).isEqualTo(child_2_1_b_expected); } private static class RepoToRepoSync extends co.codewizards.cloudstore.core.repo.sync.RepoToRepoSync { public RepoToRepoSync(final File localRoot, final URL remoteRoot) { super(localRoot, remoteRoot); } @Override protected void syncUp(final ProgressMonitor monitor) { super.syncUp(monitor); } @Override protected void syncDown(final boolean fromRepoLocalSync, final ProgressMonitor monitor) { super.syncDown(fromRepoLocalSync, monitor); } } @Test public void syncUpAndModifyFile() throws Exception { localRoot = newTestRepositoryLocalRoot("local"); localRoot.mkdirs(); remoteRoot = newTestRepositoryLocalRoot("remote"); remoteRoot.mkdirs(); final LocalRepoManager localRepoManagerLocal = localRepoManagerFactory.createLocalRepoManagerForNewRepository(localRoot); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForNewRepository(remoteRoot); localRepoManagerLocal.putRemoteRepository(localRepoManagerRemote.getRepositoryId(), getRemoteRootUrlWithPathPrefix(), localRepoManagerRemote.getPublicKey(), localPathPrefix); localRepoManagerRemote.putRemoteRepository(localRepoManagerLocal.getRepositoryId(), null, localRepoManagerLocal.getPublicKey(), remotePathPrefix); final File child_1 = createDirectory(remoteRoot, "1"); final File file_1a = createFileWithRandomContent(child_1, "a"); localRepoManagerLocal.localSync(new LoggerProgressMonitor(logger)); localRepoManagerRemote.localSync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); try (final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix());) { // This sync does down+up+down to make sure, everything is synced bidirectionally. repoToRepoSync.sync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); // The issue https://github.com/cloudstore/cloudstore/issues/25 was that when a file was modified // between the down- and the up-sync (that happens inside the normal sync(...) method), a wrong // collision was detected. So we do the steps normally happening in sync(...) individually here // and change the file inbetween. // First change the *remote* file and perform a down-sync. modifyFileRandomly(file_1a); repoToRepoSync.syncDown(true, new LoggerProgressMonitor(logger)); // Then change the same file again at the same (remote) location. modifyFileRandomly(file_1a); // Now perform the up-sync that would normally happen. This should cause the wrong collision of issue #25. localRepoManagerLocal.localSync(new NullProgressMonitor()); // We make 100% sure, the local DB is up-to-date, before up-sync. repoToRepoSync.syncUp(new LoggerProgressMonitor(logger)); // Now we sync down again. This is not really important for this test, but it usually happens in the // ordinary sync(...) method. This btw. syncs the collision file down (if there is one). // Additionally, without this down-sync, we cannot use assertDirectoriesAreEqualRecursively(...). repoToRepoSync.syncDown(true, new LoggerProgressMonitor(logger)); repoToRepoSync.close(); localRepoManagerLocal.close(); localRepoManagerRemote.close(); assertThatNoCollisionInRepo(remoteRoot); assertThatNoCollisionInRepo(localRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } } private void modifyFileRandomly(final File file) throws IOException { try { //TODO get rid of that sleep; needed, because *nix system are rounding to the next second. // Setting via file.setLastModified(time) did not solve the problem. Thread.sleep(1100); } catch (final InterruptedException e) { logger.error("Interrupted!", e); } final RandomAccessFile raf = file.createRandomAccessFile("rw"); try { if (file.length() > 0) raf.seek(random.nextInt((int)file.length())); final byte[] buf = new byte[1 + random.nextInt(10)]; random.nextBytes(buf); raf.write(buf); } finally { raf.close(); } } @Test public void syncFromRemoteToLocalWithDeletedFile() throws Exception { syncFromRemoteToLocal(); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForExistingRepository(remoteRoot); assertThat(localRepoManagerRemote).isNotNull(); final File child_2 = createFile(remoteRoot, "2"); assertThat(child_2.isDirectory()).isTrue(); final File child_2_1 = createFile(child_2, "1"); assertThat(child_2_1.isDirectory()).isTrue(); final File child_2_1_a = createFile(child_2_1, "a"); assertThat(child_2_1_a.isFile()).isTrue(); deleteFile(child_2_1_a); localRepoManagerRemote.localSync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThatFilesInRepoAreCorrect(remoteRoot); localRepoManagerRemote.close(); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } @Test public void syncFromRemoteToLocalWithDeletedDir() throws Exception { syncFromRemoteToLocal(); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForExistingRepository(remoteRoot); assertThat(localRepoManagerRemote).isNotNull(); final File child_2 = createFile(remoteRoot, "2"); assertThat(child_2.isDirectory()).isTrue(); final File child_2_1 = createFile(child_2, "1"); assertThat(child_2_1.isDirectory()).isTrue(); for (final File child : child_2_1.listFiles()) { deleteFile(child); } for (final File child : child_2.listFiles()) { deleteFile(child); } deleteFile(child_2); localRepoManagerRemote.localSync(new LoggerProgressMonitor(logger)); assertThatFilesInRepoAreCorrect(remoteRoot); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThatFilesInRepoAreCorrect(remoteRoot); localRepoManagerRemote.close(); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); File deleteThisFile = null; try { if (!getRemoteRootWithPathPrefix().exists()) { getRemoteRootWithPathPrefix().mkdirs(); assertThat(getRemoteRootWithPathPrefix().isDirectory()).isTrue(); deleteThisFile = getRemoteRootWithPathPrefix(); } assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } finally { // Just for manual checks of the directories: This should not exist. It's confusing, if it does. if (deleteThisFile != null) deleteThisFile.delete(); } } @Test public void syncWithDirectFileModificationCollision() throws Exception { syncFromRemoteToLocal(); final File r_child_2 = createFile(remoteRoot, "2"); assertThat(r_child_2.isDirectory()).isTrue(); final File r_child_2_1 = createFile(r_child_2, "1"); assertThat(r_child_2_1.isDirectory()).isTrue(); final File r_child_2_1_a = createFile(r_child_2_1, "a"); assertThat(r_child_2_1_a.isFile()).isTrue(); final File l_child_2 = createFile(localRoot, "2"); assertThat(l_child_2.isDirectory()).isTrue(); final File l_child_2_1 = createFile(l_child_2, "1"); assertThat(l_child_2_1.isDirectory()).isTrue(); final File l_child_2_1_a = createFile(l_child_2_1, "a"); assertThat(l_child_2_1_a.isFile()).isTrue(); modifyFileRandomly(r_child_2_1_a); modifyFileRandomly(l_child_2_1_a); for (int i = 0; i < 2; ++i) { // We have to sync twice to make sure the collision file is synced, too (it is created during the first sync). final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); } // Expect exactly one collision in remote repo (in directory r_child_2_1). final List<File> remoteCollisions = searchCollisions(remoteRoot); assertThat(remoteCollisions).isNotNull().hasSize(1); final File r_collision = remoteCollisions.get(0); assertThat(r_collision).isNotNull(); assertThat(r_collision.getParentFile()).isEqualTo(r_child_2_1); // Expect exactly one collision in local repo (in directory l_child_2_1). final List<File> localCollisions = searchCollisions(localRoot); assertThat(localCollisions).isNotNull().hasSize(1); final File l_collision = localCollisions.get(0); assertThat(l_collision).isNotNull(); assertThat(l_collision.getParentFile()).isEqualTo(l_child_2_1); addToFilesInRepo(remoteRoot, r_collision); assertThatFilesInRepoAreCorrect(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } private void assertThatNoCollisionInRepo(final File localRoot) { final List<File> collisions = searchCollisions(localRoot); if (!collisions.isEmpty()) Assert.fail("Collision: " + collisions.get(0)); } private List<File> searchCollisions(final File localRoot) { final List<File> collisions = new ArrayList<File>(); searchCollisions_populate(localRoot, localRoot, collisions); return collisions; } private void searchCollisions_populate(final File localRoot, final File file, final Collection<File> collisions) { final File[] children = file.listFiles(); if (children != null) { for (final File f : children) { if (f.getName().contains(IOUtil.COLLISION_FILE_NAME_INFIX)) collisions.add(f); searchCollisions_populate(localRoot, f, collisions); } } } @Test public void syncWithFileModificationInsideDeletedDirectoryCollision() throws Exception { syncFromRemoteToLocal(); final File r_child_2 = createFile(remoteRoot, "2"); assertThat(r_child_2.isDirectory()).isTrue(); final File l_child_2 = createFile(localRoot, "2"); assertThat(l_child_2.isDirectory()).isTrue(); final File l_child_2_1 = createFile(l_child_2, "1"); assertThat(l_child_2_1.isDirectory()).isTrue(); final File l_child_2_1_a = createFile(l_child_2_1, "a"); assertThat(l_child_2_1_a.isFile()).isTrue(); modifyFileRandomly(l_child_2_1_a); IOUtil.deleteDirectoryRecursively(r_child_2); for (int i = 0; i < 2; ++i) { // We have to sync twice to make sure the collision is synced, too (it is created during the first sync). final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); } final List<File> remoteCollisions = searchCollisions(remoteRoot); assertThat(remoteCollisions).isNotNull().hasSize(1); final File r_collision = remoteCollisions.get(0); assertThat(r_collision).isNotNull(); assertThat(r_collision.getParentFile()).isEqualTo(remoteRoot); assertThat(r_collision.getName()).startsWith("2."); final List<File> localCollisions = searchCollisions(localRoot); assertThat(localCollisions).isNotNull().hasSize(1); final File l_collision = localCollisions.get(0); assertThat(l_collision).isNotNull(); assertThat(l_collision.getParentFile()).isEqualTo(localRoot); assertThat(l_collision.getName()).startsWith("2."); } @Test public void syncWithFileModificationInsideDeletedDirectoryCollisionInverse() throws Exception { syncFromRemoteToLocal(); final File r_child_2 = createFile(remoteRoot, "2"); assertThat(r_child_2.isDirectory()).isTrue(); final File r_child_2_1 = createFile(r_child_2, "1"); assertThat(r_child_2_1.isDirectory()).isTrue(); final File r_child_2_1_a = createFile(r_child_2_1, "a"); assertThat(r_child_2_1_a.isFile()).isTrue(); final File l_child_2 = createFile(localRoot, "2"); assertThat(l_child_2.isDirectory()).isTrue(); modifyFileRandomly(r_child_2_1_a); IOUtil.deleteDirectoryRecursively(l_child_2); for (int i = 0; i < 2; ++i) { // We have to sync twice to make sure the collision is synced, too (it is created during the first sync). final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); } final List<File> remoteCollisions = searchCollisions(remoteRoot); assertThat(remoteCollisions).isNotNull().hasSize(1); final File r_collision = remoteCollisions.get(0); assertThat(r_collision).isNotNull(); assertThat(r_collision.getParentFile()).isEqualTo(remoteRoot); assertThat(r_collision.getName()).startsWith("2."); final List<File> localCollisions = searchCollisions(localRoot); assertThat(localCollisions).isNotNull().hasSize(1); final File l_collision = localCollisions.get(0); assertThat(l_collision).isNotNull(); assertThat(l_collision.getParentFile()).isEqualTo(localRoot); assertThat(l_collision.getName()).startsWith("2."); } // TODO test this collision: // @Test // public void syncWithDirectFileDeletionCollision() throws Exception { // // } @Test public void syncMovedFile() throws Exception { syncFromRemoteToLocal(); final File r_child_2 = createFile(remoteRoot, "2"); assertThat(r_child_2.isDirectory()).isTrue(); final File r_child_2_1 = createFile(r_child_2, "1"); assertThat(r_child_2_1.isDirectory()).isTrue(); final File r_child_2_1_b = createFile(r_child_2_1, "b"); assertThat(r_child_2_1_b.isFile()).isTrue(); final File r_child_2_b = createFile(r_child_2, "b"); assertThat(r_child_2_b.exists()).isFalse(); r_child_2_1_b.move(r_child_2_b); assertThat(r_child_2_1_b.exists()).isFalse(); assertThat(r_child_2_b.isFile()).isTrue(); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThat(r_child_2_1_b.exists()).isFalse(); assertThat(r_child_2_b.isFile()).isTrue(); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } @Test public void syncMovedFileToNewDir() throws Exception { syncFromRemoteToLocal(); final File r_child_2 = createFile(remoteRoot, "2"); assertThat(r_child_2.isDirectory()).isTrue(); final File r_child_2_1 = createFile(r_child_2, "1"); assertThat(r_child_2_1.isDirectory()).isTrue(); final File r_child_2_1_b = createFile(r_child_2_1, "b"); assertThat(r_child_2_1_b.isFile()).isTrue(); final File r_child_2_new = createFile(r_child_2, "new"); assertThat(r_child_2_new.exists()).isFalse(); r_child_2_new.mkdir(); assertThat(r_child_2_new.isDirectory()).isTrue(); final File r_child_2_new_xxx = createFile(r_child_2_new, "xxx"); r_child_2_1_b.move(r_child_2_new_xxx); assertThat(r_child_2_1_b.exists()).isFalse(); assertThat(r_child_2_new_xxx.isFile()).isTrue(); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); repoToRepoSync.close(); assertThat(r_child_2_1_b.exists()).isFalse(); assertThat(r_child_2_new_xxx.isFile()).isTrue(); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); } @Test public void syncSymlinkFile() throws Exception { localRoot = newTestRepositoryLocalRoot("local"); assertThat(localRoot.exists()).isFalse(); localRoot.mkdirs(); assertThat(localRoot.isDirectory()).isTrue(); remoteRoot = newTestRepositoryLocalRoot("remote"); assertThat(remoteRoot.exists()).isFalse(); remoteRoot.mkdirs(); assertThat(remoteRoot.isDirectory()).isTrue(); final LocalRepoManager localRepoManagerLocal = localRepoManagerFactory.createLocalRepoManagerForNewRepository(localRoot); assertThat(localRepoManagerLocal).isNotNull(); final LocalRepoManager localRepoManagerRemote = localRepoManagerFactory.createLocalRepoManagerForNewRepository(remoteRoot); assertThat(localRepoManagerRemote).isNotNull(); localRepoManagerLocal.putRemoteRepository(localRepoManagerRemote.getRepositoryId(), getRemoteRootUrlWithPathPrefix(), localRepoManagerRemote.getPublicKey(), localPathPrefix); localRepoManagerRemote.putRemoteRepository(localRepoManagerLocal.getRepositoryId(), null, localRepoManagerLocal.getPublicKey(), remotePathPrefix); localRepoManagerLocal.close(); final File child_1 = createDirectory(remoteRoot, "1"); final File child_1_a = createFileWithRandomContent(child_1, "a"); final File b = createRelativeSymlink(createFile(child_1, "b"), child_1_a); final File broken = createRelativeSymlink(createFile(child_1, "broken"), createFile(child_1, "doesNotExist")); assertThat(createFile(child_1, "broken").existsNoFollow()).isTrue(); assertThat(broken.existsNoFollow()).isTrue(); assertThat(broken.isSymbolicLink()).isTrue(); assertThat(broken.exists()).isFalse(); // following is not possible, because it's broken, i.e. exists() must be false! final long child_1_a_lastModified = System.currentTimeMillis() - (24L * 3600); final long symlink_b_lastModified = System.currentTimeMillis() - (3L * 3600); final long symlink_broken_lastModified = System.currentTimeMillis() - (7L * 3600); child_1_a.setLastModifiedNoFollow(child_1_a_lastModified); assertThat(child_1_a.getLastModifiedNoFollow()).isBetween(child_1_a_lastModified - 2000, child_1_a_lastModified + 2000); b.setLastModifiedNoFollow(symlink_b_lastModified); assertThat(b.getLastModifiedNoFollow()).isBetween(symlink_b_lastModified - 2000, symlink_b_lastModified + 2000); // Assert that changing the symlink's timestamp did not affect the real file. assertThat(child_1_a.getLastModifiedNoFollow()).isBetween(child_1_a_lastModified - 2000, child_1_a_lastModified + 2000); broken.setLastModifiedNoFollow(symlink_broken_lastModified); assertThat(broken.getLastModifiedNoFollow()).isBetween(symlink_broken_lastModified - 2000, symlink_broken_lastModified + 2000); localRepoManagerRemote.localSync(new NullProgressMonitor()); assertThatFilesInRepoAreCorrect(remoteRoot); final RepoToRepoSync repoToRepoSync = new RepoToRepoSync(getLocalRootWithPathPrefix(), getRemoteRootUrlWithPathPrefix()); repoToRepoSync.sync(new LoggerProgressMonitor(logger)); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); assertThatFilesInRepoAreCorrect(remoteRoot); assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); // delete broken symbolic link and normal symbolic link from remote // repository broken.delete(); b.delete(); // synchronize new changes repoToRepoSync.sync(new LoggerProgressMonitor(logger)); assertThatNoCollisionInRepo(localRoot); assertThatNoCollisionInRepo(remoteRoot); // compare local and remote repositories after synchronizing changes assertDirectoriesAreEqualRecursively(getLocalRootWithPathPrefix(), getRemoteRootWithPathPrefix()); repoToRepoSync.close(); localRepoManagerRemote.close(); } @Test public void syncFromRemoteToLocalWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncFromRemoteToLocal(); } @Test public void syncFromRemoteToLocalWithAddedFilesAndDirectoriesWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncFromRemoteToLocalWithAddedFilesAndDirectories(); } @Test public void syncFromRemoteToLocalWithModifiedFilesWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncFromRemoteToLocalWithModifiedFiles(); } @Test public void syncFromRemoteToLocalWithDeletedFileWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncFromRemoteToLocalWithDeletedFile(); } @Test public void syncFromRemoteToLocalWithDeletedDirWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncFromRemoteToLocalWithDeletedDir(); } @Test public void syncRemoteRootToLocalRootWithDeletedDirWithRemotePathPrefix_parentOfVirtualRootDeleted() throws Exception { remotePathPrefix = "/2/1"; syncFromRemoteToLocalWithDeletedDir(); } @Test public void syncMovedFileWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncMovedFile(); } @Test public void syncMovedFileToNewDirWithRemotePathPrefix() throws Exception { remotePathPrefix = "/2"; syncMovedFileToNewDir(); } }