blob: efe80476a1aa8eedd3cc1a1c8d06452442321056 [file] [log] [blame]
// Copyright 2024 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.skyframe;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.packages.Globber.Operation;
import com.google.devtools.build.lib.skyframe.GlobsValue.GlobRequest;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.ErrorInfo;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(TestParameterInjector.class)
public final class GlobsFunctionTest extends GlobTestBase {
@Override
protected void createGlobSkyFunction(Map<SkyFunctionName, SkyFunction> skyFunctions) {
skyFunctions.put(SkyFunctions.GLOBS, new GlobsFunction());
}
@Override
protected void assertSingleGlobMatches(
String pattern, Globber.Operation globberOperation, String... expecteds) throws Exception {
ImmutableSet<PathFragment> matchesInPathFragment =
runSingleGlob(pattern, globberOperation).getMatches();
assertThat(
matchesInPathFragment.stream()
.map(PathFragment::getPathString)
.collect(toImmutableSet()))
.isEqualTo(ImmutableSet.copyOf(expecteds));
}
@Override
protected GlobsValue runSingleGlob(String pattern, Globber.Operation globberOperation)
throws Exception {
GlobRequest globRequest = GlobRequest.create(pattern, globberOperation);
return queryGlobsValue(
GlobsValue.key(PKG_ID, Root.fromPath(root), ImmutableSet.of(globRequest)));
}
private GlobsValue queryGlobsValue(GlobsValue.Key globsKey) throws Exception {
EvaluationResult<SkyValue> result =
evaluator.evaluate(ImmutableList.of(globsKey), EVALUATION_OPTIONS);
if (result.hasError()) {
throw result.getError().getException();
}
SkyValue skyValue = result.get(globsKey);
assertThat(skyValue).isInstanceOf(GlobsValue.class);
return (GlobsValue) skyValue;
}
@Override
protected void assertIllegalPattern(String pattern) {
assertThrows(
"invalid pattern not detected: " + pattern,
InvalidGlobPatternException.class,
() -> GlobsValue.GlobRequest.create(pattern, Globber.Operation.FILES_AND_DIRS));
}
@Override
protected GlobsValue.Key createdGlobRelatedSkyKey(
String pattern, Globber.Operation globberOperation) throws InvalidGlobPatternException {
return GlobsValue.key(
PKG_ID,
Root.fromPath(root),
ImmutableSet.of(GlobRequest.create(pattern, globberOperation)));
}
@Override
protected Iterable<String> getSubpackagesMatches(String pattern) throws Exception {
SkyValue skyValue = runSingleGlob(pattern, Globber.Operation.SUBPACKAGES);
assertThat(skyValue).isInstanceOf(GlobsValue.class);
return Iterables.transform(((GlobsValue) skyValue).getMatches(), Functions.toStringFunction());
}
// The test cases below cover scenario when there are multiple GlobRequests defined.
@Test
public void testGlobs_allGlobRequestsAllSucceeds() throws Exception {
GlobRequest globRequest1 = GlobRequest.create("foo/barnacle/**", Operation.FILES_AND_DIRS);
GlobRequest globRequest2 = GlobRequest.create("foo/bar/**", Operation.FILES);
GlobRequest globRequest3 = GlobRequest.create("a2/**", Operation.SUBPACKAGES);
GlobsValue globsValue =
queryGlobsValue(
GlobsValue.key(
PKG_ID,
Root.fromPath(root),
ImmutableSet.of(globRequest1, globRequest2, globRequest3)));
assertThat(
globsValue.getMatches().stream()
.map(PathFragment::getPathString)
.collect(toImmutableSet()))
.containsExactly("a2/b2", "foo/barnacle", "foo/barnacle/wiz", "foo/bar/wiz/file");
}
@Test
public void testGlobs_oneGoodAndOneGlobUnboundedSymlink() throws Exception {
pkgPath.getRelative("parent/sub").createDirectoryAndParents();
FileSystemUtils.ensureSymbolicLink(
pkgPath.getRelative("parent/sub/symlink"), pkgPath.getRelative("parent"));
GlobRequest unboundedSymlinksGlobRequest =
GlobRequest.create("parent/sub/*", Operation.FILES_AND_DIRS);
GlobRequest goodGlobRequest = GlobRequest.create("foo/bar/**", Operation.FILES);
GlobsValue.Key globsKey =
GlobsValue.key(
PKG_ID,
Root.fromPath(root),
ImmutableSet.of(unboundedSymlinksGlobRequest, goodGlobRequest));
EvaluationResult<GlobValue> result =
evaluator.evaluate(ImmutableList.of(globsKey), EVALUATION_OPTIONS);
assertThat(result.hasError()).isTrue();
ErrorInfo errorInfo = result.getError(globsKey);
assertThat(errorInfo.getException()).isInstanceOf(FileSymlinkInfiniteExpansionException.class);
assertThat(errorInfo.getException()).hasMessageThat().contains("Infinite symlink expansion");
}
@Test
public void testGlobs_bothTwoGlobBothAreSymlinkCycles() throws Exception {
pkgPath.getRelative("parent/sub1").createDirectoryAndParents();
FileSystemUtils.ensureSymbolicLink(
pkgPath.getRelative("parent/sub1/self1"), pkgPath.getRelative("parent"));
GlobRequest symlinkGlobRequest1 = GlobRequest.create("parent/sub1/*", Operation.FILES_AND_DIRS);
pkgPath.getRelative("parent/sub2").createDirectoryAndParents();
FileSystemUtils.ensureSymbolicLink(
pkgPath.getRelative("parent/sub2/self2"), pkgPath.getRelative("parent"));
GlobRequest symlinkGlobRequest2 = GlobRequest.create("parent/sub2/*", Operation.FILES_AND_DIRS);
GlobsValue.Key globsKey =
GlobsValue.key(
PKG_ID, Root.fromPath(root), ImmutableSet.of(symlinkGlobRequest1, symlinkGlobRequest2));
EvaluationResult<GlobValue> result =
evaluator.evaluate(ImmutableList.of(globsKey), EVALUATION_OPTIONS);
assertThat(result.hasError()).isTrue();
ErrorInfo errorInfo = result.getError(globsKey);
assertThat(errorInfo.getException()).isInstanceOf(FileSymlinkInfiniteExpansionException.class);
assertThat(errorInfo.getException()).hasMessageThat().contains("Infinite symlink expansion");
// The two globs are evaluated in parallel inside GlobsFunction, so it is non-deterministic
// which SymlinkInfiniteExpansionException is thrown and caught first. So this test only needs
// to verify the output error is from either one of the SymlinkInfiniteExpansion errors.
assertThat(((FileSymlinkInfiniteExpansionException) errorInfo.getException()).getChain())
.containsAnyOf(
RootedPath.toRootedPath(Root.fromPath(root), pkgPath.getRelative("parent/sub1/self1")),
RootedPath.toRootedPath(Root.fromPath(root), pkgPath.getRelative("parent/sub2/self2")));
}
}