From 3c29bc895df192ff4bfe6ebb0355db22c6b5a5b6 Mon Sep 17 00:00:00 2001 From: ajurkowski Date: Tue, 13 Apr 2021 11:07:59 -0700 Subject: [PATCH] Add a delegate `FileSystem` implementation allowing to rewrite the paths. Add a helper `FileSystem` implementation which allows to rewrite the paths passed to the delegate. PiperOrigin-RevId: 368253791 --- .../build/lib/vfs/DelegateFileSystem.java | 252 +--------------- .../PathTransformingDelegateFileSystem.java | 285 ++++++++++++++++++ ...athTransformingDelegateFileSystemTest.java | 187 ++++++++++++ 3 files changed, 483 insertions(+), 241 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystem.java create mode 100644 src/test/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystemTest.java 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 getDirectoryEntries(PathFragment path) throws IOException { - return delegateFs.getDirectoryEntries(path); - } - - @Override - protected boolean isReadable(PathFragment path) throws IOException { - return delegateFs.isReadable(path); - } - - @Override - protected void setReadable(PathFragment path, boolean readable) throws IOException { - delegateFs.setReadable(path, readable); - } - - @Override - protected boolean isWritable(PathFragment path) throws IOException { - return delegateFs.isWritable(path); - } - - @Override - public void setWritable(PathFragment path, boolean writable) throws IOException { - delegateFs.setWritable(path, writable); - } - - @Override - protected boolean isExecutable(PathFragment path) throws IOException { - return delegateFs.isExecutable(path); - } - - @Override - protected void setExecutable(PathFragment path, boolean executable) throws IOException { - delegateFs.setExecutable(path, executable); - } - - @Override - protected InputStream getInputStream(PathFragment path) throws IOException { - return delegateFs.getInputStream(path); - } - - @Override - protected ReadableByteChannel createReadableByteChannel(PathFragment path) throws IOException { - return delegateFs.createReadableByteChannel(path); - } - - @Override - protected OutputStream getOutputStream(PathFragment path, boolean append) throws IOException { - return delegateFs.getOutputStream(path, append); - } - - @Override - public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException { - delegateFs.renameTo(sourcePath, targetPath); - } - - @Override - protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath) - throws IOException { - delegateFs.createFSDependentHardLink(linkPath, originalPath); - } - - @Override - public String getFileSystemType(PathFragment path) { - return delegateFs.getFileSystemType(path); - } - - @Override - protected void deleteTree(PathFragment path) throws IOException { - delegateFs.deleteTree(path); - } - - @Override - protected void deleteTreesBelow(PathFragment dir) throws IOException { - delegateFs.deleteTreesBelow(dir); - } - - @Override - public byte[] getxattr(PathFragment path, String name, boolean followSymlinks) - throws IOException { - return delegateFs.getxattr(path, name, followSymlinks); - } - - @Override - protected byte[] getFastDigest(PathFragment path) throws IOException { - return delegateFs.getFastDigest(path); - } - - @Override - protected byte[] getDigest(PathFragment path) throws IOException { - return delegateFs.getDigest(path); - } - - @Override - protected PathFragment resolveOneLink(PathFragment path) throws IOException { - return delegateFs.resolveOneLink(path); - } - - @Override - protected Path resolveSymbolicLinks(PathFragment path) throws IOException { - return getPath(delegateFs.resolveSymbolicLinks(path).asFragment()); - } - - @Override - protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException { - return delegateFs.stat(path, followSymlinks); - } - - @Override - protected FileStatus statNullable(PathFragment path, boolean followSymlinks) { - return delegateFs.statNullable(path, followSymlinks); - } - - @Override - protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) throws IOException { - return delegateFs.statIfFound(path, followSymlinks); - } - - @Override - protected PathFragment readSymbolicLinkUnchecked(PathFragment path) throws IOException { - return delegateFs.readSymbolicLink(path); - } - - @Override - protected Collection readdir(PathFragment path, boolean followSymlinks) - throws IOException { - return delegateFs.readdir(path, followSymlinks); - } - - @Override - protected void chmod(PathFragment path, int mode) throws IOException { - delegateFs.chmod(path, mode); + super(delegateFs); } @Override - protected void createHardLink(PathFragment linkPath, PathFragment originalPath) - throws IOException { - delegateFs.createHardLink(linkPath, originalPath); + protected final PathFragment toDelegatePath(PathFragment path) { + return path; } @Override - protected void prefetchPackageAsync(PathFragment path, int maxDirs) { - delegateFs.prefetchPackageAsync(path, maxDirs); + protected final PathFragment fromDelegatePath(PathFragment delegatePath) { + return delegatePath; } } diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystem.java new file mode 100644 index 00000000000000..c65ac5fc16dfce --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystem.java @@ -0,0 +1,285 @@ +// 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 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; + +/** + * FileSystem implementation which delegates all operations to a provided instance with a + * transformed path. + * + *

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 getDirectoryEntries(PathFragment path) throws IOException { + return delegateFs.getDirectoryEntries(toDelegatePath(path)); + } + + @Override + protected boolean isReadable(PathFragment path) throws IOException { + return delegateFs.isReadable(toDelegatePath(path)); + } + + @Override + protected void setReadable(PathFragment path, boolean readable) throws IOException { + delegateFs.setReadable(toDelegatePath(path), readable); + } + + @Override + protected boolean isWritable(PathFragment path) throws IOException { + return delegateFs.isWritable(toDelegatePath(path)); + } + + @Override + public void setWritable(PathFragment path, boolean writable) throws IOException { + delegateFs.setWritable(toDelegatePath(path), writable); + } + + @Override + protected boolean isExecutable(PathFragment path) throws IOException { + return delegateFs.isExecutable(toDelegatePath(path)); + } + + @Override + protected void setExecutable(PathFragment path, boolean executable) throws IOException { + delegateFs.setExecutable(toDelegatePath(path), executable); + } + + @Override + protected InputStream getInputStream(PathFragment path) throws IOException { + return delegateFs.getInputStream(toDelegatePath(path)); + } + + @Override + protected ReadableByteChannel createReadableByteChannel(PathFragment path) throws IOException { + return delegateFs.createReadableByteChannel(toDelegatePath(path)); + } + + @Override + protected OutputStream getOutputStream(PathFragment path, boolean append) throws IOException { + return delegateFs.getOutputStream(toDelegatePath(path), append); + } + + @Override + public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException { + delegateFs.renameTo(toDelegatePath(sourcePath), toDelegatePath(targetPath)); + } + + @Override + protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath) + throws IOException { + delegateFs.createFSDependentHardLink(toDelegatePath(linkPath), toDelegatePath(originalPath)); + } + + @Override + public String getFileSystemType(PathFragment path) { + return delegateFs.getFileSystemType(toDelegatePath(path)); + } + + @Override + protected void deleteTree(PathFragment path) throws IOException { + delegateFs.deleteTree(toDelegatePath(path)); + } + + @Override + protected void deleteTreesBelow(PathFragment dir) throws IOException { + delegateFs.deleteTreesBelow(toDelegatePath(dir)); + } + + @Override + public byte[] getxattr(PathFragment path, String name, boolean followSymlinks) + throws IOException { + return delegateFs.getxattr(toDelegatePath(path), name, followSymlinks); + } + + @Override + protected byte[] getFastDigest(PathFragment path) throws IOException { + return delegateFs.getFastDigest(toDelegatePath(path)); + } + + @Override + protected byte[] getDigest(PathFragment path) throws IOException { + return delegateFs.getDigest(toDelegatePath(path)); + } + + @Override + protected PathFragment resolveOneLink(PathFragment path) throws IOException { + return delegateFs.resolveOneLink(toDelegatePath(path)); + } + + @Override + protected Path resolveSymbolicLinks(PathFragment path) throws IOException { + return getPath( + fromDelegatePath(delegateFs.resolveSymbolicLinks(toDelegatePath(path)).asFragment())); + } + + @Override + protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException { + return delegateFs.stat(toDelegatePath(path), followSymlinks); + } + + @Override + protected FileStatus statNullable(PathFragment path, boolean followSymlinks) { + return delegateFs.statNullable(toDelegatePath(path), followSymlinks); + } + + @Override + protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) throws IOException { + return delegateFs.statIfFound(toDelegatePath(path), followSymlinks); + } + + @Override + protected PathFragment readSymbolicLinkUnchecked(PathFragment path) throws IOException { + return delegateFs.readSymbolicLinkUnchecked(toDelegatePath(path)); + } + + @Override + protected Collection readdir(PathFragment path, boolean followSymlinks) + throws IOException { + return delegateFs.readdir(toDelegatePath(path), followSymlinks); + } + + @Override + protected void chmod(PathFragment path, int mode) throws IOException { + delegateFs.chmod(toDelegatePath(path), mode); + } + + @Override + protected void createHardLink(PathFragment linkPath, PathFragment originalPath) + throws IOException { + delegateFs.createHardLink(toDelegatePath(linkPath), toDelegatePath(originalPath)); + } + + @Override + protected void prefetchPackageAsync(PathFragment path, int maxDirs) { + delegateFs.prefetchPackageAsync(toDelegatePath(path), maxDirs); + } + + /** Transform original path to a different one to be used with the {@code delegateFs}. */ + protected abstract PathFragment toDelegatePath(PathFragment path); + + /** + * Transform a path from one to be used with {@code delegateFs} to original one. + * + *

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 IGNORED = + ImmutableSet.of( + getFileSystemMethod("getPath", PathFragment.class), + getFileSystemMethod("readSymbolicLink", PathFragment.class), + getFileSystemMethod("resolveSymbolicLinks", PathFragment.class), + getFileSystemMethod("createSymbolicLink", PathFragment.class, PathFragment.class)); + + private static Method getFileSystemMethod(String name, Class... parameterTypes) { + try { + return FileSystem.class.getDeclaredMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public ImmutableList provideValues() { + return stream(FileSystem.class.getDeclaredMethods()) + .filter( + m -> + !IGNORED.contains(m) + && !Modifier.isStatic(m.getModifiers()) + && !Modifier.isFinal(m.getModifiers()) + && ImmutableList.copyOf(m.getParameterTypes()).contains(PathFragment.class)) + .map( + m -> + TestParametersValues.builder() + .name(m.getName()) + .addParameter("method", m) + .build()) + .collect(toImmutableList()); + } + } +}