| // Copyright 2017 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.actions; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.devtools.build.lib.actions.FilesetOutputTree.RelativeSymlinkBehavior.ERROR; | |
| import static com.google.devtools.build.lib.actions.FilesetOutputTree.RelativeSymlinkBehaviorWithoutError.IGNORE; | |
| import static com.google.devtools.build.lib.actions.FilesetOutputTree.RelativeSymlinkBehaviorWithoutError.RESOLVE_FULLY; | |
| import static org.junit.Assert.assertThrows; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTree.ForbiddenRelativeSymlinkException; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTree.RelativeSymlinkBehavior; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTree.RelativeSymlinkBehaviorWithoutError; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTreeTest.CommonTests; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTreeTest.OneOffTests; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTreeTest.ResolvingTests; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.testing.junit.testparameterinjector.TestParameter; | |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| import org.junit.runners.Suite; | |
| /** Tests for {@link FilesetOutputTree}. */ | |
| @RunWith(Suite.class) | |
| @Suite.SuiteClasses({CommonTests.class, OneOffTests.class, ResolvingTests.class}) | |
| public final class FilesetOutputTreeTest { | |
| private static final PathFragment EXEC_ROOT = PathFragment.create("/root"); | |
| private static FilesetOutputSymlink symlink(String from, String to) { | |
| return FilesetOutputSymlink.createForTesting( | |
| PathFragment.create(from), PathFragment.create(to), EXEC_ROOT); | |
| } | |
| private static VisitedSymlink visitedSymlink(String from, String to) { | |
| return new VisitedSymlink(PathFragment.create(from), PathFragment.create(to)); | |
| } | |
| private record VisitedSymlink(PathFragment name, PathFragment target) {} | |
| private static FilesetOutputTree createTree(FilesetOutputSymlink... symlinks) { | |
| return FilesetOutputTree.create(ImmutableList.copyOf(symlinks)); | |
| } | |
| private static ImmutableList<VisitedSymlink> visit( | |
| FilesetOutputTree filesetOutput, RelativeSymlinkBehavior relSymlinkBehavior) | |
| throws ForbiddenRelativeSymlinkException { | |
| var visited = ImmutableList.<VisitedSymlink>builder(); | |
| filesetOutput.visitSymlinks( | |
| relSymlinkBehavior, | |
| (name, target, metadata) -> visited.add(new VisitedSymlink(name, target))); | |
| return visited.build(); | |
| } | |
| private static ImmutableList<VisitedSymlink> visit( | |
| FilesetOutputTree filesetOutput, RelativeSymlinkBehaviorWithoutError relSymlinkBehavior) { | |
| var visited = ImmutableList.<VisitedSymlink>builder(); | |
| filesetOutput.visitSymlinks( | |
| relSymlinkBehavior, | |
| (name, target, metadata) -> visited.add(new VisitedSymlink(name, target))); | |
| return visited.build(); | |
| } | |
| /** Tests that apply to all relative symlink behaviors. */ | |
| @RunWith(TestParameterInjector.class) | |
| public static final class CommonTests { | |
| @TestParameter private RelativeSymlinkBehavior behavior; | |
| @Test | |
| public void empty() throws Exception { | |
| assertThat(visit(FilesetOutputTree.EMPTY, behavior)).isEmpty(); | |
| } | |
| @Test | |
| public void singleFile() throws Exception { | |
| FilesetOutputTree filesetOutput = createTree(symlink("bar", "/dir/file")); | |
| assertThat(visit(filesetOutput, behavior)) | |
| .containsExactly(visitedSymlink("bar", "/dir/file")); | |
| } | |
| @Test | |
| public void twoFiles() throws Exception { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar", "/dir/file"), symlink("baz", "/dir/file")); | |
| assertThat(visit(filesetOutput, behavior)) | |
| .containsExactly(visitedSymlink("bar", "/dir/file"), visitedSymlink("baz", "/dir/file")); | |
| } | |
| @Test | |
| public void symlinkWithExecRootRelativePath() throws Exception { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar", EXEC_ROOT.getRelative("foo/bar").getPathString())); | |
| assertThat(visit(filesetOutput, behavior)).containsExactly(visitedSymlink("bar", "foo/bar")); | |
| } | |
| } | |
| /** Tests that apply to a specific relative symlink behavior. */ | |
| @RunWith(JUnit4.class) | |
| public static final class OneOffTests { | |
| @Test | |
| public void canonicalEmptyInstance() { | |
| assertThat(createTree()).isSameInstanceAs(FilesetOutputTree.EMPTY); | |
| } | |
| @Test | |
| public void errorOnRelativeSymlink() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar", "foo"), symlink("foo", "/foo/bar")); | |
| var e = | |
| assertThrows(ForbiddenRelativeSymlinkException.class, () -> visit(filesetOutput, ERROR)); | |
| assertThat(e).hasMessageThat().contains("Fileset symlink bar -> foo is not absolute"); | |
| } | |
| @Test | |
| public void ignoredRelativeSymlink() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar", "foo"), symlink("foo", "/foo/bar")); | |
| assertThat(visit(filesetOutput, IGNORE)).containsExactly(visitedSymlink("foo", "/foo/bar")); | |
| } | |
| @Test | |
| public void resolvedRelativeDirectorySymlink() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("foo/subdir/f1", "/foo/subdir/f1"), symlink("foo/bar", "subdir")); | |
| assertThat(visit(filesetOutput, RESOLVE_FULLY)) | |
| .containsExactly( | |
| visitedSymlink("foo/subdir/f1", "/foo/subdir/f1"), | |
| visitedSymlink("foo/bar/f1", "/foo/subdir/f1")); | |
| } | |
| } | |
| /** Tests that apply resolving relative symlink behaviors. */ | |
| @RunWith(TestParameterInjector.class) | |
| public static final class ResolvingTests { | |
| @TestParameter({"RESOLVE", "RESOLVE_FULLY"}) | |
| private RelativeSymlinkBehaviorWithoutError behavior; | |
| @Test | |
| public void resolvedRelativeSymlink() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar", "foo"), symlink("foo", "/foo/bar")); | |
| assertThat(visit(filesetOutput, behavior)) | |
| .containsExactly(visitedSymlink("bar", "/foo/bar"), visitedSymlink("foo", "/foo/bar")); | |
| } | |
| @Test | |
| public void resolvedRelativeSymlinkWithDotSlash() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar", "./foo"), symlink("foo", "/foo/bar")); | |
| assertThat(visit(filesetOutput, behavior)) | |
| .containsExactly(visitedSymlink("bar", "/foo/bar"), visitedSymlink("foo", "/foo/bar")); | |
| } | |
| @Test | |
| public void symlinkCycle() { | |
| FilesetOutputTree filesetOutput = | |
| createTree( | |
| symlink("bar", "foo"), | |
| symlink("foo", "biz"), | |
| symlink("biz", "bar"), | |
| symlink("reg", "/file")); | |
| assertThat(visit(filesetOutput, behavior)).containsExactly(visitedSymlink("reg", "/file")); | |
| } | |
| @Test | |
| public void unboundedSymlinkDescendant() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("p", "a/b"), symlink("a/b", "../b/c"), symlink("reg", "/file")); | |
| assertThat(visit(filesetOutput, behavior)).containsExactly(visitedSymlink("reg", "/file")); | |
| } | |
| @Test | |
| public void unboundedSymlinkAncestor() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("a/b", "c/d"), symlink("a/c/d", ".././a"), symlink("reg", "/file")); | |
| assertThat(visit(filesetOutput, behavior)).containsExactly(visitedSymlink("reg", "/file")); | |
| } | |
| @Test | |
| public void resolvedRelativeSymlinkWithDotDotSlash() { | |
| FilesetOutputTree filesetOutput = | |
| createTree(symlink("bar/bar", "../foo/foo"), symlink("foo/foo", "/foo/bar")); | |
| assertThat(visit(filesetOutput, behavior)) | |
| .containsExactly( | |
| visitedSymlink("bar/bar", "/foo/bar"), visitedSymlink("foo/foo", "/foo/bar")); | |
| } | |
| @Test | |
| public void unresolvableRelativeSymlink() { | |
| FilesetOutputTree filesetOutput = createTree(symlink("bar", "foo")); | |
| assertThat(visit(filesetOutput, behavior)).isEmpty(); | |
| } | |
| @Test | |
| public void unresolvableRelativeSymlinkToRelativeSymlink() { | |
| FilesetOutputTree filesetOutput = createTree(symlink("bar", "foo"), symlink("foo", "baz")); | |
| assertThat(visit(filesetOutput, behavior)).isEmpty(); | |
| } | |
| } | |
| } |