Remove WalkableGraph#exists and allow WalkableGraph#getValue and WalkableGraph#getException to be given non-existent keys without crashing. Add WalkableGraph#isCycle to fill the gap in testing for the difference between non-existence and depending on a cycle. -- PiperOrigin-RevId: 143205289 MOS_MIGRATED_REVID=143205289
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java index ae7aa9d..551b098 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -43,6 +43,7 @@ import com.google.devtools.build.lib.graph.Digraph; import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException; import com.google.devtools.build.lib.packages.DependencyFilter; +import com.google.devtools.build.lib.packages.NoSuchPackageException; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.NoSuchThingException; import com.google.devtools.build.lib.packages.Package; @@ -728,9 +729,6 @@ public Target getTarget(Label label) throws TargetNotFoundException, QueryException, InterruptedException { SkyKey packageKey = PackageValue.key(label.getPackageIdentifier()); - if (!graph.exists(packageKey)) { - throw new QueryException(packageKey + " does not exist in graph"); - } try { PackageValue packageValue = (PackageValue) graph.getValue(packageKey); if (packageValue != null) { @@ -740,8 +738,16 @@ } return packageValue.getPackage().getTarget(label.getName()); } else { - throw (NoSuchThingException) Preconditions.checkNotNull( - graph.getException(packageKey), label); + NoSuchThingException exception = (NoSuchThingException) graph.getException(packageKey); + if (exception != null) { + throw exception; + } + if (graph.isCycle(packageKey)) { + throw new NoSuchPackageException( + label.getPackageIdentifier(), "Package depends on a cycle"); + } else { + throw new QueryException(packageKey + " does not exist in graph"); + } } } catch (NoSuchThingException e) { throw new TargetNotFoundException(e);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java index 9a94f3f..6fef55c 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
@@ -135,7 +135,7 @@ @Override @Nullable public SkyValue get(SkyKey key) throws InterruptedException { - return walkableGraph.exists(key) ? walkableGraph.getValue(key) : null; + return walkableGraph.getValue(key); } }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java b/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java index 5e31b91..337b14d 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java
@@ -81,22 +81,21 @@ throws NoSuchPackageException, InterruptedException { SkyKey pkgKey = PackageValue.key(packageName); - PackageValue pkgValue; - if (graph.exists(pkgKey)) { - pkgValue = (PackageValue) graph.getValue(pkgKey); - if (pkgValue == null) { - NoSuchPackageException nspe = (NoSuchPackageException) graph.getException(pkgKey); - if (nspe != null) { - throw nspe; - } - throw new NoSuchPackageException(packageName, "Package depends on a cycle"); - } + PackageValue pkgValue = (PackageValue) graph.getValue(pkgKey); + if (pkgValue != null) { + return pkgValue.getPackage(); + } + NoSuchPackageException nspe = (NoSuchPackageException) graph.getException(pkgKey); + if (nspe != null) { + throw nspe; + } + if (graph.isCycle(pkgKey)) { + throw new NoSuchPackageException(packageName, "Package depends on a cycle"); } else { // If the package key does not exist in the graph, then it must not correspond to any package, // because the SkyQuery environment has already loaded the universe. throw new BuildFileNotFoundException(packageName, "BUILD file not found on package path"); } - return pkgValue.getPackage(); } @Override @@ -139,22 +138,25 @@ public boolean isPackage(EventHandler eventHandler, PackageIdentifier packageName) throws InterruptedException { SkyKey packageLookupKey = PackageLookupValue.key(packageName); - if (!graph.exists(packageLookupKey)) { - // If the package lookup key does not exist in the graph, then it must not correspond to any - // package, because the SkyQuery environment has already loaded the universe. - return false; - } PackageLookupValue packageLookupValue = (PackageLookupValue) graph.getValue(packageLookupKey); if (packageLookupValue == null) { - Exception exception = Preconditions.checkNotNull(graph.getException(packageLookupKey), - "During package lookup for '%s', got null for exception", packageName); - if (exception instanceof NoSuchPackageException - || exception instanceof InconsistentFilesystemException) { - eventHandler.handle(Event.error(exception.getMessage())); + // Package lookups can't depend on Skyframe cycles. + Preconditions.checkState(!graph.isCycle(packageLookupKey), packageLookupKey); + Exception exception = graph.getException(packageLookupKey); + if (exception == null) { + // If the package lookup key does not exist in the graph, then it must not correspond to any + // package, because the SkyQuery environment has already loaded the universe. return false; } else { - throw new IllegalStateException("During package lookup for '" + packageName - + "', got unexpected exception type", exception); + if (exception instanceof NoSuchPackageException + || exception instanceof InconsistentFilesystemException) { + eventHandler.handle(Event.error(exception.getMessage())); + return false; + } else { + throw new IllegalStateException( + "During package lookup for '" + packageName + "', got unexpected exception type", + exception); + } } } return packageLookupValue.packageExists(); @@ -190,7 +192,7 @@ roots.addAll(pkgPath.getPathEntries()); } else { RepositoryDirectoryValue repositoryValue = - (RepositoryDirectoryValue) graph.getValue(RepositoryDirectoryValue.key(repository)); + (RepositoryDirectoryValue) graph.getValue(RepositoryDirectoryValue.key(repository)); if (repositoryValue == null) { // If this key doesn't exist, the repository is outside the universe, so we return // "nothing".
diff --git a/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java b/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java index 27475db..8abf5de 100644 --- a/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java +++ b/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java
@@ -32,26 +32,18 @@ this.graph = graph; } + @Nullable private NodeEntry getEntryForValue(SkyKey key) throws InterruptedException { NodeEntry entry = - Preconditions.checkNotNull( - graph.getBatch(null, Reason.WALKABLE_GRAPH_VALUE, ImmutableList.of(key)).get(key), - key); - Preconditions.checkState(entry.isDone(), "%s %s", key, entry); - return entry; - } - - @Override - public boolean exists(SkyKey key) throws InterruptedException { - NodeEntry entry = - graph.getBatch(null, Reason.EXISTENCE_CHECKING, ImmutableList.of(key)).get(key); - return entry != null && entry.isDone(); + graph.getBatch(null, Reason.WALKABLE_GRAPH_VALUE, ImmutableList.of(key)).get(key); + return entry != null && entry.isDone() ? entry : null; } @Nullable @Override public SkyValue getValue(SkyKey key) throws InterruptedException { - return getEntryForValue(key).getValue(); + NodeEntry entry = getEntryForValue(key); + return entry == null ? null : entry.getValue(); } private static SkyValue getValue(NodeEntry entry) throws InterruptedException { @@ -93,10 +85,24 @@ return result; } + @Override + public boolean isCycle(SkyKey key) throws InterruptedException { + NodeEntry entry = getEntryForValue(key); + if (entry == null) { + return false; + } + ErrorInfo errorInfo = entry.getErrorInfo(); + return errorInfo != null && errorInfo.getCycleInfo() != null; + } + @Nullable @Override public Exception getException(SkyKey key) throws InterruptedException { - ErrorInfo errorInfo = getEntryForValue(key).getErrorInfo(); + NodeEntry entry = getEntryForValue(key); + if (entry == null) { + return null; + } + ErrorInfo errorInfo = entry.getErrorInfo(); return errorInfo == null ? null : errorInfo.getException(); }
diff --git a/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java b/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java index 3d7796b..8f3dd04 100644 --- a/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java +++ b/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
@@ -30,17 +30,17 @@ */ @ThreadSafe public interface WalkableGraph { - - /** - * Returns whether the given key exists as a done node in the graph. If there is a chance that the - * given node does not exist, this method should be called before any others, since the others - * throw a {@link RuntimeException} on failure to access a node. - */ - boolean exists(SkyKey key) throws InterruptedException; - /** * Returns the value of the given key, or {@code null} if it has no value due to an error during - * its computation. A node with this key must exist in the graph. + * its computation or it is not done in the graph. + * + * <p>A node that is done in the graph must have either a non-null getValue, a non-null {@link + * #getException}, or a true {@link #isCycle}. + * + * <p>These three methods should all be reading the same {@link + * NodeEntry#getValueMaybeWithMetadata} value internally, so once that value is indirectly + * retrieved via one of these methods, the others can read it for free. This is relevant for graph + * implementations that may throw an {@link InterruptedException} on retrieving entries and value. */ @Nullable SkyValue getValue(SkyKey key) throws InterruptedException; @@ -63,21 +63,27 @@ /** * Returns the exception thrown when computing the node with the given key, if any. If the node - * was computed successfully, returns null. A node with this key must exist and be done in the - * graph. + * was computed successfully, depends on a cycle without any other error, or is not done in the + * graph, returns null. */ @Nullable Exception getException(SkyKey key) throws InterruptedException; /** + * Returns true if the node with the given {@code key} depends on a cycle. Returns false if the + * node does not depend on a cycle, or is not done in the graph. + */ + boolean isCycle(SkyKey key) throws InterruptedException; + + /** * Returns a map giving the direct dependencies of the nodes with the given keys. A node for each - * given key must exist and be done in the graph. + * given key must be done in the graph if it exists. */ Map<SkyKey, Iterable<SkyKey>> getDirectDeps(Iterable<SkyKey> keys) throws InterruptedException; /** * Returns a map giving the reverse dependencies of the nodes with the given keys. A node for each - * given key must exist and be done in the graph. + * given key must be done in the graph if it exists. */ Map<SkyKey, Iterable<SkyKey>> getReverseDeps(Iterable<SkyKey> keys) throws InterruptedException;
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java index 278567a..dbbcf49 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
@@ -14,6 +14,7 @@ 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 static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -27,13 +28,11 @@ 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; -import java.io.IOException; - /** Tests for {@link PrepareDepsOfPatternsFunction}. */ @RunWith(JUnit4.class) public class PrepareDepsOfPatternsFunctionSmartNegationTest extends BuildViewTestCase { @@ -60,11 +59,11 @@ patternSequence, /*successExpected=*/ true, /*keepGoing=*/ true); // Then the graph contains package values for "@//foo" and "@//foo/foo", - assertTrue(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo")))); - assertTrue(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo/foo")))); + assertTrue(exists(PackageValue.key(PackageIdentifier.parse("@//foo")), walkableGraph)); + assertTrue(exists(PackageValue.key(PackageIdentifier.parse("@//foo/foo")), walkableGraph)); // But the graph does not contain a value for the target "@//foo/foo:foofoo". - assertFalse(walkableGraph.exists(getKeyForLabel(Label.create("@//foo/foo", "foofoo")))); + assertFalse(exists(getKeyForLabel(Label.create("@//foo/foo", "foofoo")), walkableGraph)); } @Test @@ -104,14 +103,14 @@ patternSequence, /*successExpected=*/ true, /*keepGoing=*/ true); // Then the graph contains a package value for "@//foo", - assertTrue(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo")))); + assertTrue(exists(PackageValue.key(PackageIdentifier.parse("@//foo")), walkableGraph)); // But no package value for "@//foo/foo", - assertFalse(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo/foo")))); + assertFalse(exists(PackageValue.key(PackageIdentifier.parse("@//foo/foo")), walkableGraph)); // And the graph does not contain a value for the target "@//foo/foo:foofoo". Label label = Label.create("@//foo/foo", "foofoo"); - assertFalse(walkableGraph.exists(getKeyForLabel(label))); + assertFalse(exists(getKeyForLabel(label), walkableGraph)); } @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java index b29ec04..cda7fbc 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java
@@ -15,6 +15,7 @@ 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; @@ -61,7 +62,7 @@ assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo"))); // And the graph does not contain a value for the target "@//foo:foo2". - assertFalse(walkableGraph.exists(getKeyForLabel(Label.create("@//foo", "foo2")))); + assertFalse(exists(getKeyForLabel(Label.create("@//foo", "foo2")), walkableGraph)); } @Test @@ -106,7 +107,7 @@ WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); // Then the graph does not contain an entry for ":foo", - assertFalse(walkableGraph.exists(getKeyForLabel(Label.create("@//foo", "foo")))); + assertFalse(exists(getKeyForLabel(Label.create("@//foo", "foo")), walkableGraph)); } @Test @@ -188,7 +189,7 @@ assertContainsEvent("Skipping '" + bogusPattern + "': "); // And then the graph contains a value for the legit target pattern's target "@//foo:foo". - assertTrue(walkableGraph.exists(getKeyForLabel(Label.create("@//foo", "foo")))); + assertTrue(exists(getKeyForLabel(Label.create("@//foo", "foo")), walkableGraph)); } // Helpers: @@ -260,7 +261,6 @@ private static void assertValidValue( WalkableGraph graph, SkyKey key, boolean expectTransitiveException) throws InterruptedException { - assertTrue(graph.exists(key)); assertNotNull(graph.getValue(key)); if (expectTransitiveException) { assertNotNull(graph.getException(key)); @@ -271,7 +271,6 @@ private static Exception assertException(WalkableGraph graph, SkyKey key) throws InterruptedException { - assertTrue(graph.exists(key)); assertNull(graph.getValue(key)); Exception exception = graph.getException(key); assertNotNull(exception);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java index 0a8aed0..169a3fc 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java
@@ -15,6 +15,7 @@ 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 static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -34,14 +35,12 @@ import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.WalkableGraph; - +import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.IOException; - /** * Tests for {@link PrepareDepsOfTargetsUnderDirectoryFunction}. Insert excuses here. */ @@ -104,7 +103,7 @@ // Then the TransitiveTraversalValue for "@//a:a" is evaluated, SkyKey aaKey = TransitiveTraversalValue.key(Label.create("@//a", "a")); - assertThat(graph.exists(aaKey)).isTrue(); + assertThat(exists(aaKey, graph)).isTrue(); // And that TransitiveTraversalValue depends on "@//b:b.txt". Iterable<SkyKey> depsOfAa = @@ -113,7 +112,7 @@ assertThat(depsOfAa).contains(bTxtKey); // And the TransitiveTraversalValue for "b:b.txt" is evaluated. - assertThat(graph.exists(bTxtKey)).isTrue(); + assertThat(exists(bTxtKey, graph)).isTrue(); } @Test @@ -130,11 +129,11 @@ // Then the TransitiveTraversalValue for "@//a:a" is not evaluated, SkyKey aaKey = TransitiveTraversalValue.key(Label.create("@//a", "a")); - assertThat(graph.exists(aaKey)).isFalse(); + assertThat(exists(aaKey, graph)).isFalse(); // But the TransitiveTraversalValue for "@//a:aTest" is. SkyKey aaTestKey = TransitiveTraversalValue.key(Label.create("@//a", "aTest")); - assertThat(graph.exists(aaTestKey)).isTrue(); + assertThat(exists(aaTestKey, graph)).isTrue(); } /** @@ -188,13 +187,18 @@ // Also, the computation graph does not contain a cached value for "a/b". WalkableGraph graph = Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); - assertFalse(graph.exists(createPrepDepsKey(rootDirectory, excludedPathFragment, - ImmutableSet.<PathFragment>of()))); + assertFalse( + exists( + createPrepDepsKey(rootDirectory, excludedPathFragment, ImmutableSet.<PathFragment>of()), + graph)); // And the computation graph does contain a cached value for "a/c" with the empty set excluded, // because that key was evaluated. - assertTrue(graph.exists(createPrepDepsKey(rootDirectory, new PathFragment("a/c"), - ImmutableSet.<PathFragment>of()))); + assertTrue( + exists( + createPrepDepsKey( + rootDirectory, new PathFragment("a/c"), ImmutableSet.<PathFragment>of()), + graph)); } @Test @@ -233,7 +237,7 @@ // "a/b/c" does live underneath "a/b". WalkableGraph graph = Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); SkyKey abKey = createCollectPackagesKey(rootDirectory, new PathFragment("a/b"), excludedPaths); - assertThat(graph.exists(abKey)).isTrue(); + assertThat(exists(abKey, graph)).isTrue(); CollectPackagesUnderDirectoryValue abValue = (CollectPackagesUnderDirectoryValue) Preconditions.checkNotNull(graph.getValue(abKey));
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java index d6c6459..89f961a 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java
@@ -14,6 +14,7 @@ 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -31,7 +32,6 @@ import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.WalkableGraph; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -140,16 +140,18 @@ // Also, the computation graph does not contain a cached value for "a/b". WalkableGraph graph = Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); assertFalse( - graph.exists( + exists( buildRecursivePkgKey( - rootDirectory, excludedPathFragment, ImmutableSet.<PathFragment>of()))); + rootDirectory, excludedPathFragment, ImmutableSet.<PathFragment>of()), + graph)); // And the computation graph does contain a cached value for "a/c" with the empty set excluded, // because that key was evaluated. assertTrue( - graph.exists( + exists( buildRecursivePkgKey( - rootDirectory, new PathFragment("a/c"), ImmutableSet.<PathFragment>of()))); + rootDirectory, new PathFragment("a/c"), ImmutableSet.<PathFragment>of()), + graph)); } @Test @@ -176,6 +178,6 @@ // "a/b/c" does live underneath "a/b". WalkableGraph graph = Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); assertTrue( - graph.exists(buildRecursivePkgKey(rootDirectory, new PathFragment("a/b"), excludedPaths))); + exists(buildRecursivePkgKey(rootDirectory, new PathFragment("a/b"), excludedPaths), graph)); } }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeLabelVisitorTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeLabelVisitorTestCase.java index 80650fd..1fd0b74 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeLabelVisitorTestCase.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeLabelVisitorTestCase.java
@@ -14,6 +14,7 @@ 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; @@ -191,7 +192,7 @@ while (!Iterables.isEmpty(nodesToVisit)) { List<SkyKey> existingNodes = new ArrayList<>(); for (SkyKey key : nodesToVisit) { - if (graph.exists(key) && graph.getValue(key) != null && visitedNodes.add(key)) { + if (exists(key, graph) && graph.getValue(key) != null && visitedNodes.add(key)) { existingNodes.add(key); } }
diff --git a/src/test/java/com/google/devtools/build/skyframe/WalkableGraphUtils.java b/src/test/java/com/google/devtools/build/skyframe/WalkableGraphUtils.java index 7095371..05d11bf 100644 --- a/src/test/java/com/google/devtools/build/skyframe/WalkableGraphUtils.java +++ b/src/test/java/com/google/devtools/build/skyframe/WalkableGraphUtils.java
@@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -/** Utility methods for querying (r)deps of nodes from {@link WalkableGraph}s more concisely. */ +/** Utility methods for querying {@link WalkableGraph}s more concisely. */ public class WalkableGraphUtils { public static Iterable<SkyKey> getDirectDeps(WalkableGraph graph, SkyKey key) @@ -28,4 +28,8 @@ throws InterruptedException { return Iterables.getOnlyElement(graph.getReverseDeps(ImmutableList.of(key)).values()); } + + public static boolean exists(SkyKey key, WalkableGraph graph) throws InterruptedException { + return graph.getValue(key) != null || graph.getException(key) != null || graph.isCycle(key); + } }