| // 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"))); |
| } |
| } |