/** * This file is part of git-as-svn. It is subject to the license terms * in the LICENSE file found in the top-level directory of this distribution * and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn, * including this file, may be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ package svnserver.server; import com.google.common.collect.ImmutableMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.testng.Assert; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.ISVNLockHandler; import org.tmatesoft.svn.core.io.SVNRepository; import svnserver.StringHelper; import svnserver.tester.SvnTester; import svnserver.tester.SvnTesterDataProvider; import svnserver.tester.SvnTesterExternalListener; import svnserver.tester.SvnTesterFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static svnserver.SvnTestHelper.*; /** * Check svn locking. * * @author Artem V. Navrotskiy <bozaro@users.noreply.github.com> */ @Listeners(SvnTesterExternalListener.class) public class SvnLockTest { @NotNull private final static Map<String, String> propsEolNative = ImmutableMap.<String, String>builder() .put(SVNProperty.EOL_STYLE, SVNProperty.EOL_STYLE_NATIVE) .build(); /** * Check to take lock on absent file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void lockNotExists(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); lock(repo, "example2.txt", repo.getLatestRevision(), false, SVNErrorCode.FS_OUT_OF_DATE); } } /** * Check to take lock of out-of-date file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void lockOutOfDate(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); modifyFile(repo, "/example.txt", "content", latestRevision); lock(repo, "example.txt", latestRevision, false, SVNErrorCode.FS_OUT_OF_DATE); } } /** * Check to take lock of out-of-date file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void lockNotFile(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); final ISVNEditor editor = repo.getCommitEditor("Intital state", null, false, null); editor.openRoot(-1); editor.addDir("/example", null, -1); editor.addFile("/example/example.txt", null, -1); editor.changeFileProperty("/example/example.txt", SVNProperty.EOL_STYLE, SVNPropertyValue.create(SVNProperty.EOL_STYLE_NATIVE)); sendDeltaAndClose(editor, "/example/example.txt", null, "Source content"); editor.closeDir(); editor.closeDir(); editor.closeEdit(); final long latestRevision = repo.getLatestRevision(); lock(repo, "example", latestRevision, false, SVNErrorCode.FS_NOT_FILE); } } /** * Check to stealing lock. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void lockForce(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); SVNLock oldLock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(oldLock); compareLock(oldLock, repo.getLock("example.txt")); SVNLock badLock = lock(repo, "example.txt", latestRevision, false, SVNErrorCode.FS_PATH_ALREADY_LOCKED); Assert.assertNull(badLock); compareLock(oldLock, repo.getLock("example.txt")); SVNLock newLock = lock(repo, "example.txt", latestRevision, true, null); Assert.assertNotNull(newLock); compareLock(newLock, repo.getLock("example.txt")); } } /** * Check to break lock. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void unlockForce(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); SVNLock oldLock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(oldLock); unlock(repo, oldLock, false, null); SVNLock newLock = lock(repo, "example.txt", latestRevision, true, null); Assert.assertNotNull(newLock); compareLock(newLock, repo.getLock("example.txt")); unlock(repo, oldLock, true, null); Assert.assertNull(repo.getLock("example.txt")); } } /** * Check to take lock of out-of-date file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void lockSimple(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); createFile(repo, "/example2.txt", "", propsEolNative); Assert.assertNull(repo.getLock("example.txt")); // New lock final SVNLock lock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(lock); compareLock(repo.getLock("example.txt"), lock); // Already locked lock(repo, "example.txt", latestRevision, false, SVNErrorCode.FS_PATH_ALREADY_LOCKED); // Lock must not changed compareLock(repo.getLock("example.txt"), lock); unlock(repo, lock, false, null); Assert.assertNull(repo.getLock("example.txt")); // Lock again lock(repo, "example.txt", latestRevision, false, null); } } /** * Check for deny modify locking file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void modifyLocked(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(lock); try { modifyFile(repo, "/example.txt", "content", latestRevision); Assert.fail(); } catch (SVNException e) { Assert.assertEquals(e.getErrorMessage().getErrorCode(), SVNErrorCode.FS_BAD_LOCK_TOKEN); } } } /** * Check for deny modify locking file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void modifyLockedInvalidLock(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock oldLock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(oldLock); unlock(repo, oldLock, false, null); final SVNLock newLock = lock(repo, "example.txt", latestRevision, false, null); try { final Map<String, String> locks = new HashMap<>(); locks.put(oldLock.getPath(), oldLock.getID()); final ISVNEditor editor = repo.getCommitEditor("Intital state", locks, false, null); editor.openRoot(-1); editor.openFile("/example.txt", latestRevision); sendDeltaAndClose(editor, "/example.txt", "", "Source content"); editor.closeDir(); editor.closeEdit(); Assert.fail(); } catch (SVNException e) { Assert.assertEquals(e.getErrorMessage().getErrorCode(), SVNErrorCode.FS_BAD_LOCK_TOKEN); } compareLock(server.openSvnRepository().getLock("/example.txt"), newLock); } } /** * Check for commit with keep locks. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void modifyLockedRemoveLock(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(lock); { final Map<String, String> locks = new HashMap<>(); locks.put("/example.txt", lock.getID()); final ISVNEditor editor = repo.getCommitEditor("Intital state", locks, false, null); editor.openRoot(-1); editor.openFile("/example.txt", latestRevision); sendDeltaAndClose(editor, "/example.txt", "", "Source content"); editor.closeDir(); editor.closeEdit(); } Assert.assertNull(repo.getLock("/example.txt")); } } /** * Check for commit with remove locks. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void modifyLockedKeepLock(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(lock); { final Map<String, String> locks = new HashMap<>(); locks.put("/example.txt", lock.getID()); final ISVNEditor editor = repo.getCommitEditor("Intital state", locks, true, null); editor.openRoot(-1); editor.openFile("/example.txt", latestRevision); sendDeltaAndClose(editor, "/example.txt", "", "Source content"); editor.closeDir(); editor.closeEdit(); } compareLock(repo.getLock("/example.txt"), lock); } } /** * Check for deny modify locking file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void deleteLocked(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(lock); try { final ISVNEditor editor = repo.getCommitEditor("Intital state", null, false, null); editor.openRoot(-1); editor.deleteEntry("/example.txt", latestRevision); editor.closeDir(); editor.closeEdit(); Assert.fail(); } catch (SVNException e) { Assert.assertEquals(e.getErrorMessage().getErrorCode(), SVNErrorCode.FS_BAD_LOCK_TOKEN); } } } /** * Check for deny modify locking file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void deleteLockedDirNoLock(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); { final ISVNEditor editor = repo.getCommitEditor("Intital state", null, false, null); editor.openRoot(-1); editor.addDir("/example", null, -1); editor.addFile("/example/example.txt", null, -1); editor.changeFileProperty("/example/example.txt", SVNProperty.EOL_STYLE, SVNPropertyValue.create(SVNProperty.EOL_STYLE_NATIVE)); sendDeltaAndClose(editor, "/example/example.txt", null, "Source content"); editor.closeDir(); editor.closeDir(); editor.closeEdit(); } final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock = lock(repo, "/example/example.txt", latestRevision, false, null); Assert.assertNotNull(lock); try { final ISVNEditor editor = repo.getCommitEditor("Intital state", null, false, null); editor.openRoot(-1); editor.deleteEntry("/example", latestRevision); editor.closeDir(); editor.closeEdit(); Assert.fail(); } catch (SVNException e) { Assert.assertEquals(e.getErrorMessage().getErrorCode(), SVNErrorCode.FS_BAD_LOCK_TOKEN); } } } /** * Check get locks. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void getLocks(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); { final ISVNEditor editor = repo.getCommitEditor("Intital state", null, false, null); editor.openRoot(-1); editor.addDir("/example", null, -1); editor.addFile("/example/example.txt", null, -1); editor.changeFileProperty("/example/example.txt", SVNProperty.EOL_STYLE, SVNPropertyValue.create(SVNProperty.EOL_STYLE_NATIVE)); sendDeltaAndClose(editor, "/example/example.txt", null, "Source content"); editor.closeDir(); editor.addFile("/foo.txt", null, -1); editor.changeFileProperty("/foo.txt", SVNProperty.EOL_STYLE, SVNPropertyValue.create(SVNProperty.EOL_STYLE_NATIVE)); sendDeltaAndClose(editor, "/foo.txt", null, "Source content"); editor.closeDir(); editor.closeEdit(); } compareLocks(repo.getLocks("")); final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock1 = lock(repo, "/example/example.txt", latestRevision, false, null); Assert.assertNotNull(lock1); final SVNLock lock2 = lock(repo, "/foo.txt", latestRevision, false, null); Assert.assertNotNull(lock2); compareLocks(repo.getLocks(""), lock1, lock2); } } /** * Check for deny modify locking file. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void deleteLockedDirWithLock(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); { final ISVNEditor editor = repo.getCommitEditor("Intital state", null, false, null); editor.openRoot(-1); editor.addDir("/example", null, -1); editor.addFile("/example/example.txt", null, -1); editor.changeFileProperty("/example/example.txt", SVNProperty.EOL_STYLE, SVNPropertyValue.create(SVNProperty.EOL_STYLE_NATIVE)); sendDeltaAndClose(editor, "/example/example.txt", null, "Source content"); editor.closeDir(); editor.closeDir(); editor.closeEdit(); } final long latestRevision = repo.getLatestRevision(); // Lock final SVNLock lock = lock(repo, "/example/example.txt", latestRevision, false, null); Assert.assertNotNull(lock); final Map<String, String> locks = new HashMap<>(); locks.put(lock.getPath(), lock.getID()); final ISVNEditor editor = repo.getCommitEditor("Intital state", locks, false, null); editor.openRoot(-1); editor.deleteEntry("/example", latestRevision); editor.closeDir(); editor.closeEdit(); } } /** * Try to twice remove lock. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void unlockTwice(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // New lock final SVNLock lock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(lock); unlock(repo, lock, false, null); unlock(repo, lock, false, SVNErrorCode.FS_NO_SUCH_LOCK); } } /** * Try to remove not-owned lock. * * @throws Exception */ @Test(dataProvider = "all", dataProviderClass = SvnTesterDataProvider.class) public void unlockNotOwner(@NotNull SvnTesterFactory factory) throws Exception { try (SvnTester server = factory.create()) { final SVNRepository repo = server.openSvnRepository(); createFile(repo, "/example.txt", "", propsEolNative); final long latestRevision = repo.getLatestRevision(); // New lock final SVNLock oldLock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(oldLock); unlock(repo, oldLock, false, null); final SVNLock newLock = lock(repo, "example.txt", latestRevision, false, null); Assert.assertNotNull(newLock); unlock(repo, oldLock, false, SVNErrorCode.FS_NO_SUCH_LOCK); } } private void compareLock(@Nullable SVNLock actual, @Nullable SVNLock expeted) { if (expeted == null) { Assert.assertNull(actual); } else { Assert.assertNotNull(actual); Assert.assertEquals(actual.getID(), expeted.getID()); } } private void compareLocks(SVNLock[] actual, SVNLock... expected) { Map<String, SVNLock> actualLocks = new HashMap<>(); for (SVNLock lock : actual) { actualLocks.put(lock.getPath(), lock); } for (SVNLock lock : expected) { compareLock(actualLocks.remove(lock.getPath()), lock); } Assert.assertTrue(actualLocks.isEmpty()); } @Nullable private SVNLock lock(@NotNull SVNRepository repo, @NotNull String path, long revision, boolean force, @Nullable SVNErrorCode errorCode) { final Map<String, Long> pathsToRevisions = new HashMap<>(); pathsToRevisions.put(path, revision); final List<SVNLock> locks = new ArrayList<>(); try { repo.lock(pathsToRevisions, null, force, new ISVNLockHandler() { @Override public void handleLock(@NotNull String path, @Nullable SVNLock lock, @Nullable SVNErrorMessage error) throws SVNException { if (error != null) { throw new SVNException(error); } Assert.assertNull(errorCode); Assert.assertNotNull(lock); locks.add(lock); } @Override public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException { Assert.fail(); } }); Assert.assertNull(errorCode); Assert.assertTrue(locks.size() <= 1); return locks.isEmpty() ? null : locks.get(0); } catch (SVNException e) { Assert.assertEquals(e.getErrorMessage().getErrorCode(), errorCode); return null; } } private void unlock(@NotNull SVNRepository repo, @NotNull SVNLock lock, boolean breakLock, @Nullable SVNErrorCode errorCode) { try { final Map<String, String> pathsToTokens = new HashMap<>(); final String root = repo.getLocation().getPath().substring(repo.getRepositoryRoot(true).getPath().length()); Assert.assertTrue(lock.getPath().startsWith(root)); pathsToTokens.put(StringHelper.normalize(lock.getPath().substring(root.length())), lock.getID()); repo.unlock(pathsToTokens, breakLock, new ISVNLockHandler() { @Override public void handleLock(@NotNull String path, @Nullable SVNLock lock, @Nullable SVNErrorMessage error) throws SVNException { Assert.fail(); } @Override public void handleUnlock(String path, SVNLock removedLock, SVNErrorMessage error) throws SVNException { if (error != null) { throw new SVNException(error); } Assert.assertNull(errorCode); Assert.assertNotNull(removedLock); compareLock(removedLock, lock); } }); Assert.assertNull(errorCode); } catch (SVNException e) { Assert.assertEquals(e.getErrorMessage().getErrorCode(), errorCode); } } }