diff --git a/src/main/java/com/google/devtools/build/lib/vfs/DelegateFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/DelegateFileSystem.java index c29d05068762cb..b1315141a75521 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/DelegateFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/DelegateFileSystem.java @@ -14,13 +14,6 @@ // package com.google.devtools.build.lib.vfs; -import com.google.common.base.Preconditions; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.ReadableByteChannel; -import java.util.Collection; - /** * A file system that delegates all operations to {@code delegateFs} under the hood. * @@ -34,248 +27,25 @@ * return rpc.getFileSize(path, followSymlinks); * } * + * + *
The implementation uses {@link PathTransformingDelegateFileSystem} with identity path
+ * transformations ({@linkplain PathTransformingDelegateFileSystem#toDelegatePath(PathFragment)
+ * toDelegatePath} and {@linkplain PathTransformingDelegateFileSystem#fromDelegatePath(PathFragment)
+ * fromDelegatePath}).
*/
-public abstract class DelegateFileSystem extends FileSystem {
-
- protected final FileSystem delegateFs;
+public abstract class DelegateFileSystem extends PathTransformingDelegateFileSystem {
public DelegateFileSystem(FileSystem delegateFs) {
- super(Preconditions.checkNotNull(delegateFs, "delegateFs").getDigestFunction());
- this.delegateFs = delegateFs;
- }
-
- @Override
- public boolean supportsModifications(PathFragment path) {
- return delegateFs.supportsModifications(path);
- }
-
- @Override
- public boolean supportsSymbolicLinksNatively(PathFragment path) {
- return delegateFs.supportsSymbolicLinksNatively(path);
- }
-
- @Override
- protected boolean supportsHardLinksNatively(PathFragment path) {
- return delegateFs.supportsHardLinksNatively(path);
- }
-
- @Override
- public boolean isFilePathCaseSensitive() {
- return delegateFs.isFilePathCaseSensitive();
- }
-
- @Override
- public boolean createDirectory(PathFragment path) throws IOException {
- return delegateFs.createDirectory(path);
- }
-
- @Override
- public void createDirectoryAndParents(PathFragment path) throws IOException {
- delegateFs.createDirectoryAndParents(path);
- }
-
- @Override
- protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException {
- return delegateFs.getFileSize(path, followSymlinks);
- }
-
- @Override
- protected boolean delete(PathFragment path) throws IOException {
- return delegateFs.delete(path);
- }
-
- @Override
- protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException {
- return delegateFs.getLastModifiedTime(path, followSymlinks);
- }
-
- @Override
- public void setLastModifiedTime(PathFragment path, long newTime) throws IOException {
- delegateFs.setLastModifiedTime(path, newTime);
- }
-
- @Override
- protected boolean isSymbolicLink(PathFragment path) {
- return delegateFs.isSymbolicLink(path);
- }
-
- @Override
- protected boolean isDirectory(PathFragment path, boolean followSymlinks) {
- return delegateFs.isDirectory(path, followSymlinks);
- }
-
- @Override
- protected boolean isFile(PathFragment path, boolean followSymlinks) {
- return delegateFs.isFile(path, followSymlinks);
- }
-
- @Override
- protected boolean isSpecialFile(PathFragment path, boolean followSymlinks) {
- return delegateFs.isSpecialFile(path, followSymlinks);
- }
-
- @Override
- protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
- throws IOException {
- delegateFs.createSymbolicLink(linkPath, targetFragment);
- }
-
- @Override
- protected PathFragment readSymbolicLink(PathFragment path) throws IOException {
- return delegateFs.readSymbolicLink(path);
- }
-
- @Override
- protected boolean exists(PathFragment path, boolean followSymlinks) {
- return delegateFs.exists(path, followSymlinks);
- }
-
- @Override
- public boolean exists(PathFragment path) {
- return delegateFs.exists(path);
- }
-
- @Override
- protected Collection Please consider using {@link DelegateFileSystem} if you don't need to transform the paths.
+ */
+public abstract class PathTransformingDelegateFileSystem extends FileSystem {
+
+ protected final FileSystem delegateFs;
+
+ public PathTransformingDelegateFileSystem(FileSystem delegateFs) {
+ super(Preconditions.checkNotNull(delegateFs, "delegateFs").getDigestFunction());
+ this.delegateFs = delegateFs;
+ }
+
+ @Override
+ public boolean supportsModifications(PathFragment path) {
+ return delegateFs.supportsModifications(toDelegatePath(path));
+ }
+
+ @Override
+ public boolean supportsSymbolicLinksNatively(PathFragment path) {
+ return delegateFs.supportsSymbolicLinksNatively(toDelegatePath(path));
+ }
+
+ @Override
+ protected boolean supportsHardLinksNatively(PathFragment path) {
+ return delegateFs.supportsHardLinksNatively(toDelegatePath(path));
+ }
+
+ @Override
+ public boolean isFilePathCaseSensitive() {
+ return delegateFs.isFilePathCaseSensitive();
+ }
+
+ @Override
+ public boolean createDirectory(PathFragment path) throws IOException {
+ return delegateFs.createDirectory(toDelegatePath(path));
+ }
+
+ @Override
+ public void createDirectoryAndParents(PathFragment path) throws IOException {
+ delegateFs.createDirectoryAndParents(toDelegatePath(path));
+ }
+
+ @Override
+ protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException {
+ return delegateFs.getFileSize(toDelegatePath(path), followSymlinks);
+ }
+
+ @Override
+ protected boolean delete(PathFragment path) throws IOException {
+ return delegateFs.delete(toDelegatePath(path));
+ }
+
+ @Override
+ protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException {
+ return delegateFs.getLastModifiedTime(toDelegatePath(path), followSymlinks);
+ }
+
+ @Override
+ public void setLastModifiedTime(PathFragment path, long newTime) throws IOException {
+ delegateFs.setLastModifiedTime(toDelegatePath(path), newTime);
+ }
+
+ @Override
+ protected boolean isSymbolicLink(PathFragment path) {
+ return delegateFs.isSymbolicLink(toDelegatePath(path));
+ }
+
+ @Override
+ protected boolean isDirectory(PathFragment path, boolean followSymlinks) {
+ return delegateFs.isDirectory(toDelegatePath(path), followSymlinks);
+ }
+
+ @Override
+ protected boolean isFile(PathFragment path, boolean followSymlinks) {
+ return delegateFs.isFile(toDelegatePath(path), followSymlinks);
+ }
+
+ @Override
+ protected boolean isSpecialFile(PathFragment path, boolean followSymlinks) {
+ return delegateFs.isSpecialFile(toDelegatePath(path), followSymlinks);
+ }
+
+ @Override
+ protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
+ throws IOException {
+ delegateFs.createSymbolicLink(toDelegatePath(linkPath), targetFragment);
+ }
+
+ @Override
+ protected PathFragment readSymbolicLink(PathFragment path) throws IOException {
+ return fromDelegatePath(delegateFs.readSymbolicLink(toDelegatePath(path)));
+ }
+
+ @Override
+ protected boolean exists(PathFragment path, boolean followSymlinks) {
+ return delegateFs.exists(toDelegatePath(path), followSymlinks);
+ }
+
+ @Override
+ public boolean exists(PathFragment path) {
+ return delegateFs.exists(toDelegatePath(path));
+ }
+
+ @Override
+ protected Collection We expect that for each {@code path}: {@code
+ * fromDelegatePath(toDelegatePath(path)).equals(path)}.
+ */
+ protected abstract PathFragment fromDelegatePath(PathFragment delegatePath);
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystemTest.java
new file mode 100644
index 00000000000000..b375ff8a8a8326
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystemTest.java
@@ -0,0 +1,187 @@
+// Copyright 2021 The Bazel Authors. 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 com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Arrays.stream;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import com.google.testing.junit.testparameterinjector.TestParameters;
+import com.google.testing.junit.testparameterinjector.TestParameters.TestParametersValues;
+import com.google.testing.junit.testparameterinjector.TestParameters.TestParametersValuesProvider;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for PathTransformingDelegateFileSystem. Make sure all methods rewrite paths. */
+@RunWith(TestParameterInjector.class)
+public class PathTransformingDelegateFileSystemTest {
+ private final FileSystem delegateFileSystem = createMockFileSystem();
+ private final TestDelegateFileSystem fileSystem = new TestDelegateFileSystem(delegateFileSystem);
+
+ private static FileSystem createMockFileSystem() {
+ FileSystem fileSystem = mock(FileSystem.class);
+ when(fileSystem.getDigestFunction()).thenReturn(DigestHashFunction.SHA256);
+ when(fileSystem.getPath(any(PathFragment.class))).thenCallRealMethod();
+ return fileSystem;
+ }
+
+ @Before
+ public void verifyGetDigestFunctionCalled() {
+ // getDigestFunction gets called in the constructor of PathTransformingDelegateFileSystem, make
+ // sure to "consume" that so that tests don't need to account for that.
+ verify(delegateFileSystem).getDigestFunction();
+ }
+
+ @Test
+ @TestParameters(valuesProvider = FileSystemMethodProvider.class)
+ public void simplePathMethod_callsDelegateWithRewrittenPath(Method method) throws Exception {
+ PathFragment path = PathFragment.create("/original/dir/file");
+
+ method.invoke(fileSystem, pathAndDefaultArgs(method, path));
+
+ method.invoke(
+ verify(delegateFileSystem),
+ pathAndDefaultArgs(method, PathFragment.create("/transformed/dir/file")));
+ verifyNoMoreInteractions(delegateFileSystem);
+ }
+
+ @Test
+ public void readSymbolicLink_callsDelegateWithRewrittenPathAndTransformsItBack()
+ throws Exception {
+ PathFragment path = PathFragment.create("/original/dir/file");
+ when(delegateFileSystem.readSymbolicLink(PathFragment.create("/transformed/dir/file")))
+ .thenReturn(PathFragment.create("/transformed/resolved"));
+
+ PathFragment resolvedPath = fileSystem.readSymbolicLink(path);
+
+ assertThat(resolvedPath).isEqualTo(PathFragment.create("/original/resolved"));
+ }
+
+ @Test
+ public void resolveSymbolicLinks_callsDelegateWithRewrittenPathAndTransformsItBack()
+ throws Exception {
+ PathFragment path = PathFragment.create("/original/dir/file");
+ when(delegateFileSystem.resolveSymbolicLinks(PathFragment.create("/transformed/dir/file")))
+ .thenReturn(Path.create("/transformed/resolved", delegateFileSystem));
+
+ Path resolvedPath = fileSystem.resolveSymbolicLinks(path);
+
+ assertThat(resolvedPath.asFragment()).isEqualTo(PathFragment.create("/original/resolved"));
+ assertThat(resolvedPath.getFileSystem()).isSameInstanceAs(fileSystem);
+ }
+
+ @Test
+ public void createSymbolicLink_callsDelegateWithRewrittenPathNotTarget() throws Exception {
+ PathFragment target = PathFragment.create("/original/target");
+
+ fileSystem.createSymbolicLink(PathFragment.create("/original/dir/file"), target);
+
+ verify(delegateFileSystem)
+ .createSymbolicLink(PathFragment.create("/transformed/dir/file"), target);
+ verifyNoMoreInteractions(delegateFileSystem);
+ }
+
+ private static final ImmutableClassToInstanceMap> DEFAULT_VALUES =
+ ImmutableClassToInstanceMap.builder()
+ .put(boolean.class, false)
+ .put(int.class, 0)
+ .put(long.class, 0L)
+ .put(String.class, "")
+ .build();
+
+ private static Object[] pathAndDefaultArgs(Method method, PathFragment path) {
+ Class>[] types = method.getParameterTypes();
+ Object[] result = new Object[types.length];
+ for (int i = 0; i < types.length; ++i) {
+ if (types[i].equals(PathFragment.class)) {
+ result[i] = path.replaceName(path.getBaseName() + i);
+ continue;
+ }
+ result[i] =
+ checkNotNull(
+ DEFAULT_VALUES.get(types[i]), "Missing default value for: %s", types[i].getName());
+ }
+ return result;
+ }
+
+ private static class TestDelegateFileSystem extends PathTransformingDelegateFileSystem {
+
+ private static final PathFragment ORIGINAL = PathFragment.create("/original");
+ private static final PathFragment TRANSFORMED = PathFragment.create("/transformed");
+
+ TestDelegateFileSystem(FileSystem fileSystem) {
+ super(fileSystem);
+ }
+
+ @Override
+ protected PathFragment toDelegatePath(PathFragment path) {
+ return TRANSFORMED.getRelative(path.relativeTo(ORIGINAL));
+ }
+
+ @Override
+ protected PathFragment fromDelegatePath(PathFragment delegatePath) {
+ return ORIGINAL.getRelative(delegatePath.relativeTo(TRANSFORMED));
+ }
+ }
+
+ private static class FileSystemMethodProvider implements TestParametersValuesProvider {
+
+ private static final ImmutableSet