aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java')
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java806
1 files changed, 806 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
new file mode 100644
index 0000000000..6c8071f075
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
@@ -0,0 +1,806 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.vfs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+
+/**
+ * Generic tests for any file system that implements {@link ScopeEscapableFileSystem},
+ * i.e. any file system that supports symlinks that escape its scope.
+ *
+ * Each suitable file system test should inherit from this class, thereby obtaining
+ * all the tests.
+ */
+public abstract class ScopeEscapableFileSystemTest extends SymlinkAwareFileSystemTest {
+
+ /**
+ * Trivial FileSystem implementation that can record the last path passed to each method
+ * and read/write to a unified "state" variable (which can then be checked by tests) for
+ * each data type this class manipulates.
+ *
+ * The default implementation of each method throws an exception. Each test case should
+ * selectively override the methods it expects to be invoked.
+ */
+ private static class TestDelegator extends FileSystem {
+ protected Path lastPath;
+ protected boolean booleanState;
+ protected long longState;
+ protected Object objectState;
+
+ public void setState(boolean state) { booleanState = state; }
+ public void setState(long state) { longState = state; }
+ public void setState(Object state) { objectState = state; }
+
+ public boolean booleanState() { return booleanState; }
+ public long longState() { return longState; }
+ public Object objectState() { return objectState; }
+
+ public PathFragment lastPath() {
+ Path ans = lastPath;
+ // Clear this out to protect against accidental matches when testing the same path multiple
+ // consecutive times.
+ lastPath = null;
+ return ans != null ? ans.asFragment() : null;
+ }
+
+ @Override public boolean supportsModifications() { return true; }
+ @Override public boolean supportsSymbolicLinks() { return true; }
+
+ private static RuntimeException re() {
+ return new RuntimeException("This method should not be called in this context");
+ }
+
+ @Override protected boolean isReadable(Path path) { throw re(); }
+ @Override protected boolean isWritable(Path path) { throw re(); }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { throw re(); }
+ @Override protected boolean isFile(Path path, boolean followSymlinks) { throw re(); }
+ @Override protected boolean isExecutable(Path path) { throw re(); }
+ @Override protected boolean exists(Path path, boolean followSymlinks) {throw re(); }
+ @Override protected boolean isSymbolicLink(Path path) { throw re(); }
+ @Override protected boolean createDirectory(Path path) { throw re(); }
+ @Override protected boolean delete(Path path) { throw re(); }
+
+ @Override protected long getFileSize(Path path, boolean followSymlinks) { throw re(); }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { throw re(); }
+
+ @Override protected void setWritable(Path path, boolean writable) { throw re(); }
+ @Override protected void setExecutable(Path path, boolean executable) { throw re(); }
+ @Override protected void setReadable(Path path, boolean readable) { throw re(); }
+ @Override protected void setLastModifiedTime(Path path, long newTime) { throw re(); }
+ @Override protected void renameTo(Path sourcePath, Path targetPath) { throw re(); }
+ @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) {
+ throw re();
+ }
+
+ @Override protected PathFragment readSymbolicLink(Path path) { throw re(); }
+ @Override protected InputStream getInputStream(Path path) { throw re(); }
+ @Override protected Collection<Path> getDirectoryEntries(Path path) { throw re(); }
+ @Override protected OutputStream getOutputStream(Path path, boolean append) { throw re(); }
+ @Override
+ protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException {
+ throw re();
+ }
+ }
+
+ protected static final PathFragment SCOPE_ROOT = new PathFragment("/fs/root");
+
+ private Path fileLink;
+ private PathFragment fileLinkTarget;
+ private Path dirLink;
+ private PathFragment dirLinkTarget;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ Preconditions.checkState(testFS instanceof ScopeEscapableFileSystem,
+ "Not ScopeEscapable: " + testFS);
+ ((ScopeEscapableFileSystem) testFS).enableScopeChecking(false);
+ for (int i = 1; i <= SCOPE_ROOT.segmentCount(); i++) {
+ testFS.getPath(SCOPE_ROOT.subFragment(0, i)).createDirectory();
+ }
+
+ fileLink = testFS.getPath(SCOPE_ROOT.getRelative("link"));
+ fileLinkTarget = new PathFragment("/should/be/delegated/fileLinkTarget");
+ testFS.createSymbolicLink(fileLink, fileLinkTarget);
+
+ dirLink = testFS.getPath(SCOPE_ROOT.getRelative("dirlink"));
+ dirLinkTarget = new PathFragment("/should/be/delegated/dirLinkTarget");
+ testFS.createSymbolicLink(dirLink, dirLinkTarget);
+ }
+
+ /**
+ * Returns the file system supplied by {@link #getFreshFileSystem}, cast to
+ * a {@link ScopeEscapableFileSystem}. Also enables scope checking within
+ * the file system (which we keep disabled for inherited tests that aren't
+ * intended to test scope boundaries).
+ */
+ private ScopeEscapableFileSystem scopedFS() {
+ ScopeEscapableFileSystem fs = (ScopeEscapableFileSystem) testFS;
+ fs.enableScopeChecking(true);
+ return fs;
+ }
+
+ // Checks that the semi-resolved path passed to the delegator matches the expected value.
+ private void checkPath(TestDelegator delegator, PathFragment expectedDelegatedPath) {
+ assertTrue(expectedDelegatedPath.equals(delegator.lastPath()));
+ }
+
+ // Asserts that the condition is false and checks that the expected path was delegated.
+ private void assertFalseWithPathCheck(boolean result, TestDelegator delegator,
+ PathFragment expectedDelegatedPath) {
+ assertFalse(result);
+ checkPath(delegator, expectedDelegatedPath);
+ }
+
+ // Asserts that the condition is true and checks that the expected path was delegated.
+ private void assertTrueWithPathCheck(boolean result, TestDelegator delegator,
+ PathFragment expectedDelegatedPath) {
+ assertTrue(result);
+ checkPath(delegator, expectedDelegatedPath);
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Tests:
+ /////////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void testIsReadableCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isReadable(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isReadable(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isReadable(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isReadable(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isReadable(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsWritableCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isWritable(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isWritable(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isWritable(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isWritable(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isWritable(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testisExecutableCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isExecutable(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isExecutable(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isExecutable(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isExecutable(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isExecutable(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsDirectoryCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean exists(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isDirectory(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isDirectory(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsFileCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isFile(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean exists(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isFile(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isFile(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isFile(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isFile(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsSymbolicLinkCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isSymbolicLink(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean exists(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ // We shouldn't follow final-segment links, so they should never invoke the delegator.
+ delegator.setState(false);
+ assertTrue(fileLink.isSymbolicLink());
+ assertTrue(delegator.lastPath() == null);
+
+ assertFalseWithPathCheck(dirLink.getRelative("a").isSymbolicLink(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isSymbolicLink(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ /**
+ * Returns a test delegator that reflects info passed to Path.exists() calls.
+ */
+ private TestDelegator newExistsDelegator() {
+ return new TestDelegator() {
+ @Override protected boolean exists(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected FileStatus stat(Path path, boolean followSymlinks) throws IOException {
+ if (!exists(path, followSymlinks)) {
+ throw new IOException("Expected exception on stat of non-existent file");
+ }
+ return super.stat(path, followSymlinks);
+ }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ }
+
+ @Test
+ public void testExistsCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = newExistsDelegator();
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.exists(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").exists(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.exists(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").exists(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCreateDirectoryCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean createDirectory(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(dirLink.getRelative("a").createDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(dirLink.getRelative("a").createDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testDeleteCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean delete(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertTrue(fileLink.delete());
+ assertTrue(delegator.lastPath() == null); // Deleting a link shouldn't require delegation.
+ assertFalseWithPathCheck(dirLink.getRelative("a").delete(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(dirLink.getRelative("a").delete(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallGetFileSizeOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected long getFileSize(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return longState();
+ }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ final int state1 = 10;
+ delegator.setState(state1);
+ assertEquals(state1, fileLink.getFileSize());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state1, dirLink.getRelative("a").getFileSize());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+
+ final int state2 = 10;
+ delegator.setState(state2);
+ assertEquals(state2, fileLink.getFileSize());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state2, dirLink.getRelative("a").getFileSize());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallGetLastModifiedTimeOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return longState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ final int state1 = 10;
+ delegator.setState(state1);
+ assertEquals(state1, fileLink.getLastModifiedTime());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state1, dirLink.getRelative("a").getLastModifiedTime());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+
+ final int state2 = 10;
+ delegator.setState(state2);
+ assertEquals(state2, fileLink.getLastModifiedTime());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state2, dirLink.getRelative("a").getLastModifiedTime());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetReadableOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setReadable(Path path, boolean readable) {
+ lastPath = path;
+ setState(readable);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ fileLink.setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+
+ delegator.setState(false);
+ dirLink.getRelative("a").setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetWritableOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setWritable(Path path, boolean writable) {
+ lastPath = path;
+ setState(writable);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ fileLink.setWritable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setWritable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+
+ delegator.setState(false);
+ dirLink.getRelative("a").setWritable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setWritable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetExecutableOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setReadable(Path path, boolean readable) {
+ lastPath = path;
+ setState(readable);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ fileLink.setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+
+ delegator.setState(false);
+ dirLink.getRelative("a").setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetLastModifiedTimeOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setLastModifiedTime(Path path, long newTime) {
+ lastPath = path;
+ setState(newTime);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(0);
+ fileLink.setLastModifiedTime(10);
+ assertEquals(10, delegator.longState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setLastModifiedTime(15);
+ assertEquals(15, delegator.longState());
+ checkPath(delegator, fileLinkTarget);
+
+ dirLink.getRelative("a").setLastModifiedTime(20);
+ assertEquals(20, delegator.longState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setLastModifiedTime(25);
+ assertEquals(25, delegator.longState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallRenameToOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void renameTo(Path sourcePath, Path targetPath) {
+ lastPath = sourcePath;
+ setState(targetPath);
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ // Renaming a link should work fine.
+ delegator.setState(null);
+ fileLink.renameTo(testFS.getPath(SCOPE_ROOT).getRelative("newname"));
+ assertEquals(null, delegator.lastPath()); // Renaming a link shouldn't require delegation.
+ assertEquals(null, delegator.objectState());
+
+ // Renaming an out-of-scope path to an in-scope path should fail due to filesystem mismatch
+ // errors.
+ Path newPath = testFS.getPath(SCOPE_ROOT.getRelative("blah"));
+ try {
+ dirLink.getRelative("a").renameTo(newPath);
+ fail("This is an attempt at a cross-filesystem renaming, which should fail");
+ } catch (IOException e) {
+ // Expected.
+ }
+
+ // Renaming an out-of-scope path to another out-of-scope path can be valid.
+ newPath = dirLink.getRelative("b");
+ dirLink.getRelative("a").renameTo(newPath);
+ assertEquals(dirLinkTarget.getRelative("a"), delegator.lastPath());
+ assertEquals(dirLinkTarget.getRelative("b"), ((Path) delegator.objectState()).asFragment());
+ }
+
+ @Test
+ public void testCallCreateSymbolicLinkOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) {
+ lastPath = linkPath;
+ setState(targetFragment);
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ PathFragment newLinkTarget = new PathFragment("/something/else");
+ dirLink.getRelative("a").createSymbolicLink(newLinkTarget);
+ assertEquals(dirLinkTarget.getRelative("a"), delegator.lastPath());
+ assertSame(newLinkTarget, delegator.objectState());
+ }
+
+ @Test
+ public void testCallReadSymbolicLinkOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected PathFragment readSymbolicLink(Path path) {
+ lastPath = path;
+ return (PathFragment) objectState;
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ // Since we're not following the link, this shouldn't invoke delegation.
+ delegator.setState(new PathFragment("whatever"));
+ PathFragment p = fileLink.readSymbolicLink();
+ assertEquals(null, delegator.lastPath());
+ assertNotSame(delegator.objectState(), p);
+
+ // This should.
+ p = dirLink.getRelative("a").readSymbolicLink();
+ assertEquals(dirLinkTarget.getRelative("a"), delegator.lastPath());
+ assertSame(delegator.objectState(), p);
+ }
+
+ @Test
+ public void testCallGetInputStreamOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected InputStream getInputStream(Path path) {
+ lastPath = path;
+ return (InputStream) objectState;
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(new ByteArrayInputStream("blah".getBytes()));
+ InputStream is = fileLink.getInputStream();
+ assertEquals(fileLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), is);
+
+ delegator.setState(new ByteArrayInputStream("blah2".getBytes()));
+ is = dirLink.getInputStream();
+ assertEquals(dirLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), is);
+ }
+
+ @Test
+ public void testCallGetOutputStreamOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected OutputStream getOutputStream(Path path, boolean append) {
+ lastPath = path;
+ return (OutputStream) objectState;
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(new ByteArrayOutputStream());
+ OutputStream os = fileLink.getOutputStream();
+ assertEquals(fileLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), os);
+
+ delegator.setState(new ByteArrayOutputStream());
+ os = dirLink.getOutputStream();
+ assertEquals(dirLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), os);
+ }
+
+ @Test
+ public void testCallGetDirectoryEntriesOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected Collection<Path> getDirectoryEntries(Path path) {
+ lastPath = path;
+ return ImmutableList.of((Path) objectState);
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(testFS.getPath("/anything"));
+ Collection<Path> entries = dirLink.getDirectoryEntries();
+ assertEquals(dirLinkTarget, delegator.lastPath());
+ assertEquals(1, entries.size());
+ assertSame(delegator.objectState(), entries.iterator().next());
+ }
+
+ /**
+ * Asserts that "link" is an in-scope link that doesn't result in an out-of-FS
+ * delegation. If link is relative, its path is relative to SCOPE_ROOT.
+ *
+ * Note that we don't actually check that the canonicalized target path matches
+ * the link's target value. Such testing should be covered by
+ * SymlinkAwareFileSystemTest.
+ */
+ private void assertInScopeLink(String link, String target, TestDelegator d) throws IOException {
+ Path l = testFS.getPath(SCOPE_ROOT.getRelative(link));
+ testFS.createSymbolicLink(l, new PathFragment(target));
+ l.exists();
+ assertNull(d.lastPath());
+ }
+
+ /**
+ * Asserts that "link" is an out-of-scope link and that the re-delegated path
+ * matches expectedPath. If link is relative, its path is relative to SCOPE_ROOT.
+ */
+ private void assertOutOfScopeLink(String link, String target, String expectedPath,
+ TestDelegator d) throws IOException {
+ Path l = testFS.getPath(SCOPE_ROOT.getRelative(link));
+ testFS.createSymbolicLink(l, new PathFragment(target));
+ l.exists();
+ assertEquals(expectedPath, d.lastPath().getPathString());
+ }
+
+ /**
+ * Returns the scope root with the final n segments chopped off (or a 0-segment path
+ * if n > SCOPE_ROOT.segmentCount()).
+ */
+ private String chopScopeRoot(int n) {
+ return SCOPE_ROOT
+ .subFragment(0, n > SCOPE_ROOT.segmentCount() ? 0 : SCOPE_ROOT.segmentCount() - n)
+ .getPathString();
+ }
+
+ /**
+ * Tests that absolute symlinks with ".." and "." segments are delegated to
+ * the expected paths.
+ */
+ @Test
+ public void testAbsoluteSymlinksWithParentReferences() throws Exception {
+ TestDelegator d = newExistsDelegator();
+ scopedFS().setDelegator(d);
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir")));
+ String scopeRoot = SCOPE_ROOT.getPathString();
+ String scopeBase = SCOPE_ROOT.getBaseName();
+
+ // Symlinks that should never escape our scope.
+ assertInScopeLink("ilink1", scopeRoot, d);
+ assertInScopeLink("ilink2", scopeRoot + "/target", d);
+ assertInScopeLink("ilink3", scopeRoot + "/dir/../target", d);
+ assertInScopeLink("ilink4", scopeRoot + "/dir/../dir/dir2/../target", d);
+ assertInScopeLink("ilink5", scopeRoot + "/./dir/.././target", d);
+ assertInScopeLink("ilink6", scopeRoot + "/../" + scopeBase + "/target", d);
+ assertInScopeLink("ilink7", "/some/path/../.." + scopeRoot + "/target", d);
+
+ // Symlinks that should escape our scope.
+ assertOutOfScopeLink("olink1", scopeRoot + "/../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink2", "/some/other/path", "/some/other/path", d);
+ assertOutOfScopeLink("olink3", scopeRoot + "/../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink4", chopScopeRoot(1) + "/target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink5", scopeRoot + "/../../../../target", "/target", d);
+
+ // In-scope symlink that's not the final segment in a query.
+ Path iDirLink = testFS.getPath(SCOPE_ROOT.getRelative("ilinkdir"));
+ testFS.createSymbolicLink(iDirLink, SCOPE_ROOT.getRelative("dir"));
+ iDirLink.getRelative("file").exists();
+ assertNull(d.lastPath());
+
+ // Out-of-scope symlink that's not the final segment in a query.
+ Path oDirLink = testFS.getPath(SCOPE_ROOT.getRelative("olinkdir"));
+ testFS.createSymbolicLink(oDirLink, new PathFragment("/some/other/dir"));
+ oDirLink.getRelative("file").exists();
+ assertEquals("/some/other/dir/file", d.lastPath().getPathString());
+ }
+
+ /**
+ * Tests that relative symlinks with ".." and "." segments are delegated to
+ * the expected paths.
+ */
+ @Test
+ public void testRelativeSymlinksWithParentReferences() throws Exception {
+ TestDelegator d = newExistsDelegator();
+ scopedFS().setDelegator(d);
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir")));
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2")));
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/dir3")));
+ String scopeRoot = SCOPE_ROOT.getPathString();
+ String scopeBase = SCOPE_ROOT.getBaseName();
+
+ // Symlinks that should never escape our scope.
+ assertInScopeLink("ilink1", "target", d);
+ assertInScopeLink("ilink2", "dir/../otherdir/target", d);
+ assertInScopeLink("dir/ilink3", "../target", d);
+ assertInScopeLink("dir/dir2/ilink4", "../../target", d);
+ assertInScopeLink("dir/dir2/ilink5", ".././../dir/./target", d);
+ assertInScopeLink("dir/dir2/ilink6", "../dir2/../../dir/dir2/dir3/../../../target", d);
+
+ // Symlinks that should escape our scope.
+ assertOutOfScopeLink("olink1", "../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("dir/olink2", "../../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink3", "../" + scopeBase + "/target", scopeRoot + "/target", d);
+ assertOutOfScopeLink("dir/dir2/olink5", "../../../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("dir/dir2/olink6", "../dir2/../../dir/dir2/../../../target",
+ chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("dir/olink7", "../../../target", chopScopeRoot(2) + "target", d);
+ assertOutOfScopeLink("olink8", "../../../../../target", "/target", d);
+
+ // In-scope symlink that's not the final segment in a query.
+ Path iDirLink = testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/ilinkdir"));
+ testFS.createSymbolicLink(iDirLink, new PathFragment("../../dir"));
+ iDirLink.getRelative("file").exists();
+ assertNull(d.lastPath());
+
+ // Out-of-scope symlink that's not the final segment in a query.
+ Path oDirLink = testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/olinkdir"));
+ testFS.createSymbolicLink(oDirLink, new PathFragment("../../../other/dir"));
+ oDirLink.getRelative("file").exists();
+ assertEquals(chopScopeRoot(1) + "/other/dir/file", d.lastPath().getPathString());
+ }
+}