| // 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.EvaluationResultSubjectFactory.assertThatEvaluationResult; | |
| import static com.google.devtools.build.skyframe.WalkableGraphUtils.exists; | |
| import static org.junit.Assert.assertFalse; | |
| import static org.junit.Assert.assertNotNull; | |
| import static org.junit.Assert.assertNull; | |
| import static org.junit.Assert.assertTrue; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.eventbus.EventBus; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.events.Reporter; | |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; | |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; | |
| import com.google.devtools.build.lib.util.Preconditions; | |
| import com.google.devtools.build.skyframe.EvaluationResult; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import com.google.devtools.build.skyframe.WalkableGraph; | |
| import java.io.IOException; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link com.google.devtools.build.lib.skyframe.PrepareDepsOfPatternsFunction}. */ | |
| @RunWith(JUnit4.class) | |
| public class PrepareDepsOfPatternsFunctionTest extends BuildViewTestCase { | |
| private static SkyKey getKeyForLabel(Label label) { | |
| // Note that these tests used to look for TargetMarker SkyKeys before TargetMarker was | |
| // inlined in TransitiveTraversalFunction. Because TargetMarker is now inlined, it doesn't | |
| // appear in the graph. Instead, these tests now look for TransitiveTraversal keys. | |
| return TransitiveTraversalValue.key(label); | |
| } | |
| @Test | |
| public void testFunctionLoadsTargetAndNotUnspecifiedTargets() throws Exception { | |
| // Given a package "//foo" with independent target rules ":foo" and ":foo2", | |
| createFooAndFoo2(/*dependent=*/ false); | |
| // Given a target pattern sequence consisting of a single-target pattern for "//foo", | |
| ImmutableList<String> patternSequence = ImmutableList.of("//foo"); | |
| // When PrepareDepsOfPatternsFunction successfully completes evaluation, | |
| WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); | |
| // Then the graph contains a value for the target "@//foo:foo", | |
| assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo"))); | |
| // And the graph does not contain a value for the target "@//foo:foo2". | |
| assertFalse(exists(getKeyForLabel(Label.create("@//foo", "foo2")), walkableGraph)); | |
| } | |
| @Test | |
| public void testFunctionLoadsTargetDependencies() throws Exception { | |
| // Given a package "//foo" with target rules ":foo" and ":foo2", | |
| // And given ":foo" depends on ":foo2", | |
| createFooAndFoo2(/*dependent=*/ true); | |
| // Given a target pattern sequence consisting of a single-target pattern for "//foo", | |
| ImmutableList<String> patternSequence = ImmutableList.of("//foo"); | |
| // When PrepareDepsOfPatternsFunction successfully completes evaluation, | |
| WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); | |
| // Then the graph contains an entry for ":foo"'s dependency, ":foo2". | |
| assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo2"))); | |
| } | |
| @Test | |
| public void testFunctionExpandsTargetPatterns() throws Exception { | |
| // Given a package "@//foo" with independent target rules ":foo" and ":foo2", | |
| createFooAndFoo2(/*dependent=*/ false); | |
| // Given a target pattern sequence consisting of a pattern for "//foo:*", | |
| ImmutableList<String> patternSequence = ImmutableList.of("//foo:*"); | |
| // When PrepareDepsOfPatternsFunction successfully completes evaluation, | |
| WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); | |
| // Then the graph contains an entry for ":foo" and ":foo2". | |
| assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo"))); | |
| assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo2"))); | |
| } | |
| @Test | |
| public void testTargetParsingException() throws Exception { | |
| // Given no packages, and a target pattern sequence referring to a non-existent target, | |
| String nonexistentTarget = "//foo:foo"; | |
| ImmutableList<String> patternSequence = ImmutableList.of(nonexistentTarget); | |
| // When PrepareDepsOfPatternsFunction completes evaluation, | |
| WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); | |
| // Then the graph does not contain an entry for ":foo", | |
| assertFalse(exists(getKeyForLabel(Label.create("@//foo", "foo")), walkableGraph)); | |
| } | |
| @Test | |
| public void testDependencyTraversalNoSuchPackageException() throws Exception { | |
| // Given a package "//foo" with a target ":foo" that has a dependency on a non-existent target | |
| // "//bar:bar" in a non-existent package "//bar", | |
| createFooWithDependencyOnMissingBarPackage(); | |
| // Given a target pattern sequence consisting of a single-target pattern for "//foo", | |
| ImmutableList<String> patternSequence = ImmutableList.of("//foo"); | |
| // When PrepareDepsOfPatternsFunction completes evaluation, | |
| WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); | |
| // Then the graph contains an entry for ":foo", | |
| assertValidValue( | |
| walkableGraph, | |
| getKeyForLabel(Label.create("@//foo", "foo")), | |
| /*expectTransitiveException=*/ true); | |
| // And an entry with a NoSuchPackageException for "//bar:bar", | |
| Exception e = assertException(walkableGraph, getKeyForLabel(Label.create("@//bar", "bar"))); | |
| assertThat(e).isInstanceOf(NoSuchPackageException.class); | |
| } | |
| @Test | |
| public void testDependencyTraversalNoSuchTargetException() throws Exception { | |
| // Given a package "//foo" with a target ":foo" that has a dependency on a non-existent target | |
| // "//bar:bar" in an existing package "//bar", | |
| createFooWithDependencyOnBarPackageWithMissingTarget(); | |
| // Given a target pattern sequence consisting of a single-target pattern for "//foo", | |
| ImmutableList<String> patternSequence = ImmutableList.of("//foo"); | |
| // When PrepareDepsOfPatternsFunction completes evaluation, | |
| WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); | |
| // Then the graph contains an entry for ":foo" which has both a value and an exception, | |
| assertValidValue( | |
| walkableGraph, | |
| getKeyForLabel(Label.create("@//foo", "foo")), | |
| /*expectTransitiveException=*/ true); | |
| // And an entry with a NoSuchTargetException for "//bar:bar", | |
| Exception e = assertException(walkableGraph, getKeyForLabel(Label.create("@//bar", "bar"))); | |
| assertThat(e).isInstanceOf(NoSuchTargetException.class); | |
| } | |
| @Test | |
| public void testParsingProblemsKeepGoing() throws Exception { | |
| parsingProblem(/*keepGoing=*/ true); | |
| } | |
| /** | |
| * PrepareDepsOfPatternsFunction always keeps going despite any target pattern parsing errors, in | |
| * keeping with the original behavior of {@link WalkableGraph.WalkableGraphFactory#prepareAndGet}, | |
| * which always used {@code keepGoing=true} during target pattern parsing because it was | |
| * responsible for ensuring that queries had a complete graph to work on. | |
| */ | |
| @Test | |
| public void testParsingProblemsNoKeepGoing() throws Exception { | |
| parsingProblem(/*keepGoing=*/ false); | |
| } | |
| private void parsingProblem(boolean keepGoing) throws Exception { | |
| // Given a package "//foo" with target rule ":foo", | |
| createFooAndFoo2(/*dependent=*/ false); | |
| // Given a target pattern sequence consisting of a pattern with parsing problems followed by | |
| // a legit target pattern, | |
| String bogusPattern = "//foo/...."; | |
| ImmutableList<String> patternSequence = ImmutableList.of(bogusPattern, "//foo:foo"); | |
| // When PrepareDepsOfPatternsFunction runs in the selected keep-going mode, | |
| WalkableGraph walkableGraph = | |
| getGraphFromPatternsEvaluation(patternSequence, /*keepGoing=*/ keepGoing); | |
| // Then it skips evaluation of the malformed target pattern, but logs about it, | |
| assertContainsEvent("Skipping '" + bogusPattern + "': "); | |
| // And then the graph contains a value for the legit target pattern's target "@//foo:foo". | |
| assertTrue(exists(getKeyForLabel(Label.create("@//foo", "foo")), walkableGraph)); | |
| } | |
| // Helpers: | |
| private WalkableGraph getGraphFromPatternsEvaluation(ImmutableList<String> patternSequence) | |
| throws InterruptedException { | |
| return getGraphFromPatternsEvaluation(patternSequence, /*keepGoing=*/ true); | |
| } | |
| private WalkableGraph getGraphFromPatternsEvaluation( | |
| ImmutableList<String> patternSequence, boolean keepGoing) throws InterruptedException { | |
| SkyKey independentTarget = PrepareDepsOfPatternsValue.key(patternSequence, ""); | |
| ImmutableList<SkyKey> singletonTargetPattern = ImmutableList.of(independentTarget); | |
| // When PrepareDepsOfPatternsFunction completes evaluation, | |
| EvaluationResult<SkyValue> evaluationResult = | |
| getSkyframeExecutor() | |
| .getDriverForTesting() | |
| .evaluate( | |
| singletonTargetPattern, | |
| keepGoing, | |
| LOADING_PHASE_THREADS, | |
| new Reporter(new EventBus(), eventCollector)); | |
| // Currently all callers either expect success or pass keepGoing=true, which implies success, | |
| // since PrepareDepsOfPatternsFunction swallows all errors. Will need to be changed if a test | |
| // that evaluates with keepGoing=false and expects errors is added. | |
| assertThatEvaluationResult(evaluationResult).hasNoError(); | |
| return Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); | |
| } | |
| private void createFooAndFoo2(boolean dependent) throws IOException { | |
| String dependencyIfAny = dependent ? "srcs = [':foo2']," : ""; | |
| scratch.file( | |
| "foo/BUILD", | |
| "genrule(name = 'foo',", | |
| dependencyIfAny, | |
| " outs = ['out.txt'],", | |
| " cmd = 'touch $@')", | |
| "genrule(name = 'foo2',", | |
| " outs = ['out2.txt'],", | |
| " cmd = 'touch $@')"); | |
| } | |
| private void createFooWithDependencyOnMissingBarPackage() throws IOException { | |
| scratch.file( | |
| "foo/BUILD", | |
| "genrule(name = 'foo',", | |
| " srcs = ['//bar:bar'],", | |
| " outs = ['out.txt'],", | |
| " cmd = 'touch $@')"); | |
| } | |
| private void createFooWithDependencyOnBarPackageWithMissingTarget() throws IOException { | |
| scratch.file( | |
| "foo/BUILD", | |
| "genrule(name = 'foo',", | |
| " srcs = ['//bar:bar'],", | |
| " outs = ['out.txt'],", | |
| " cmd = 'touch $@')"); | |
| scratch.file("bar/BUILD"); | |
| } | |
| private static void assertValidValue(WalkableGraph graph, SkyKey key) | |
| throws InterruptedException { | |
| assertValidValue(graph, key, /*expectTransitiveException=*/ false); | |
| } | |
| /** | |
| * A node in the walkable graph may have both a value and an exception. This happens when one of a | |
| * node's transitive dependencies throws an exception, but its parent recovers from it. | |
| */ | |
| private static void assertValidValue( | |
| WalkableGraph graph, SkyKey key, boolean expectTransitiveException) | |
| throws InterruptedException { | |
| assertNotNull(graph.getValue(key)); | |
| if (expectTransitiveException) { | |
| assertNotNull(graph.getException(key)); | |
| } else { | |
| assertNull(graph.getException(key)); | |
| } | |
| } | |
| private static Exception assertException(WalkableGraph graph, SkyKey key) | |
| throws InterruptedException { | |
| assertNull(graph.getValue(key)); | |
| Exception exception = graph.getException(key); | |
| assertNotNull(exception); | |
| return exception; | |
| } | |
| } |