| // Copyright 2015 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.truth.Truth.assertThat; |
| import static com.google.devtools.build.skyframe.WalkableGraphUtils.exists; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.vfs.Path; |
| 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.EvaluationContext; |
| import com.google.devtools.build.skyframe.EvaluationResult; |
| import com.google.devtools.build.skyframe.SkyKey; |
| import com.google.devtools.build.skyframe.WalkableGraph; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Tests for {@link RecursivePkgFunction}. Unfortunately, we can't directly test |
| * RecursivePkgFunction as it uses PackageValues, and PackageFunction uses legacy stuff that |
| * isn't easily mockable. So our testing strategy is to make hacky calls to |
| * SequencedSkyframeExecutor. |
| * |
| * <p>Target parsing tests already cover most of the behavior of RecursivePkgFunction, but there |
| * are a couple of corner cases we need to test directly. |
| */ |
| @RunWith(JUnit4.class) |
| public class RecursivePkgFunctionTest extends BuildViewTestCase { |
| |
| private static SkyKey buildRecursivePkgKey( |
| Path root, PathFragment rootRelativePath, ImmutableSet<PathFragment> excludedPaths) { |
| RootedPath rootedPath = RootedPath.toRootedPath(Root.fromPath(root), rootRelativePath); |
| return RecursivePkgValue.key( |
| RepositoryName.MAIN, rootedPath, excludedPaths); |
| } |
| |
| private RecursivePkgValue buildRecursivePkgValue(Path root, PathFragment rootRelativePath) |
| throws Exception { |
| return buildRecursivePkgValue(root, rootRelativePath, ImmutableSet.of()); |
| } |
| |
| private RecursivePkgValue buildRecursivePkgValue( |
| Path root, PathFragment rootRelativePath, ImmutableSet<PathFragment> excludedPaths) |
| throws Exception { |
| SkyKey key = buildRecursivePkgKey(root, rootRelativePath, excludedPaths); |
| return getEvaluationResult(key).get(key); |
| } |
| |
| private EvaluationResult<RecursivePkgValue> getEvaluationResult(SkyKey key) |
| throws InterruptedException { |
| EvaluationContext evaluationContext = |
| EvaluationContext.newBuilder() |
| .setKeepGoing(false) |
| .setNumThreads(SequencedSkyframeExecutor.DEFAULT_THREAD_COUNT) |
| .setEventHandler(reporter) |
| .build(); |
| EvaluationResult<RecursivePkgValue> evaluationResult = |
| skyframeExecutor.getEvaluator().evaluate(ImmutableList.of(key), evaluationContext); |
| Preconditions.checkState(!evaluationResult.hasError()); |
| return evaluationResult; |
| } |
| |
| @Test |
| public void testStartingAtBuildFile() throws Exception { |
| scratch.file("a/b/c/BUILD"); |
| RecursivePkgValue value = |
| buildRecursivePkgValue(rootDirectory, PathFragment.create("a/b/c/BUILD")); |
| assertThat(value.getPackages().isEmpty()).isTrue(); |
| } |
| |
| @Test |
| public void testPackagesUnderMultipleRoots() throws Exception { |
| // PackageLoader doesn't support --package_path. |
| initializeSkyframeExecutor(/*doPackageLoadingChecks=*/ false); |
| skyframeExecutor = getSkyframeExecutor(); |
| |
| Path root1 = rootDirectory.getRelative("root1"); |
| Path root2 = rootDirectory.getRelative("root2"); |
| scratch.file(root1 + "/WORKSPACE"); |
| scratch.file(root2 + "/WORKSPACE"); |
| scratch.file(root1 + "/a/BUILD"); |
| scratch.file(root2 + "/a/b/BUILD"); |
| setPackageOptions("--package_path=" + "root1" + ":" + "root2"); |
| |
| RecursivePkgValue valueForRoot1 = buildRecursivePkgValue(root1, PathFragment.create("a")); |
| String root1Pkg = valueForRoot1.getPackages().getSingleton(); |
| assertThat(root1Pkg).isEqualTo("a"); |
| |
| RecursivePkgValue valueForRoot2 = buildRecursivePkgValue(root2, PathFragment.create("a")); |
| String root2Pkg = valueForRoot2.getPackages().getSingleton(); |
| assertThat(root2Pkg).isEqualTo("a/b"); |
| } |
| |
| @Test |
| public void testSubdirectoryExclusion() throws Exception { |
| // Given a package "a" with two packages below it, "a/b" and "a/c", |
| scratch.file("a/BUILD"); |
| scratch.file("a/b/BUILD"); |
| scratch.file("a/c/BUILD"); |
| |
| // When the top package is evaluated for recursive package values, and "a/b" is excluded, |
| PathFragment excludedPathFragment = PathFragment.create("a/b"); |
| SkyKey key = |
| buildRecursivePkgKey( |
| rootDirectory, PathFragment.create("a"), ImmutableSet.of(excludedPathFragment)); |
| EvaluationResult<RecursivePkgValue> evaluationResult = getEvaluationResult(key); |
| RecursivePkgValue value = evaluationResult.get(key); |
| |
| // Then the package corresponding to "a/b" is not present in the result, |
| assertThat(value.getPackages().toList()).doesNotContain("a/b"); |
| |
| // And the "a" package and "a/c" package are. |
| assertThat(value.getPackages().toList()).contains("a"); |
| assertThat(value.getPackages().toList()).contains("a/c"); |
| |
| // Also, the computation graph does not contain a cached value for "a/b". |
| WalkableGraph graph = Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); |
| assertThat( |
| exists( |
| buildRecursivePkgKey(rootDirectory, excludedPathFragment, ImmutableSet.of()), |
| graph)) |
| .isFalse(); |
| |
| // And the computation graph does contain a cached value for "a/c" with the empty set excluded, |
| // because that key was evaluated. |
| assertThat( |
| exists( |
| buildRecursivePkgKey(rootDirectory, PathFragment.create("a/c"), ImmutableSet.of()), |
| graph)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void testExcludedSubdirectoryGettingPassedDown() throws Exception { |
| // Given a package "a" with two packages below a directory below it, "a/b/c" and "a/b/d", |
| scratch.file("a/BUILD"); |
| scratch.file("a/b/c/BUILD"); |
| scratch.file("a/b/d/BUILD"); |
| |
| // When the top package is evaluated for recursive package values, and "a/b/c" is excluded, |
| ImmutableSet<PathFragment> excludedPaths = ImmutableSet.of(PathFragment.create("a/b/c")); |
| SkyKey key = buildRecursivePkgKey(rootDirectory, PathFragment.create("a"), excludedPaths); |
| EvaluationResult<RecursivePkgValue> evaluationResult = getEvaluationResult(key); |
| RecursivePkgValue value = evaluationResult.get(key); |
| |
| // Then the package corresponding to the excluded subdirectory is not present in the result, |
| assertThat(value.getPackages().toList()).doesNotContain("a/b/c"); |
| |
| // And the top package and other subsubdirectory package are. |
| assertThat(value.getPackages().toList()).contains("a"); |
| assertThat(value.getPackages().toList()).contains("a/b/d"); |
| |
| // Also, the computation graph contains a cached value for "a/b" with "a/b/c" excluded, because |
| // "a/b/c" does live underneath "a/b". |
| WalkableGraph graph = Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); |
| assertThat( |
| exists( |
| buildRecursivePkgKey(rootDirectory, PathFragment.create("a/b"), excludedPaths), |
| graph)) |
| .isTrue(); |
| } |
| } |