| // Copyright 2023 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.lib.actions.util.ActionsTestUtil.createArtifact; | |
| import static org.mockito.Mockito.never; | |
| import static org.mockito.Mockito.verify; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.Sets; | |
| import com.google.devtools.build.lib.actions.Action; | |
| import com.google.devtools.build.lib.actions.ArtifactRoot; | |
| import com.google.devtools.build.lib.actions.ArtifactRoot.RootType; | |
| import com.google.devtools.build.lib.actions.BasicActionLookupValue; | |
| import com.google.devtools.build.lib.actions.cache.ActionCache; | |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.skyframe.SkyframeFocuser.FocusResult; | |
| import com.google.devtools.build.skyframe.AbstractSkyKey; | |
| import com.google.devtools.build.skyframe.GraphTester.StringValue; | |
| import com.google.devtools.build.skyframe.InMemoryGraph; | |
| import com.google.devtools.build.skyframe.NodeEntry; | |
| import com.google.devtools.build.skyframe.QueryableGraph.Reason; | |
| import com.google.devtools.build.skyframe.SkyFunctionName; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import com.google.devtools.build.skyframe.Version; | |
| import java.util.Set; | |
| import org.junit.Rule; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| import org.mockito.Mock; | |
| import org.mockito.junit.MockitoJUnit; | |
| import org.mockito.junit.MockitoRule; | |
| /** Tests for {@link SkyframeFocuser}. */ | |
| @RunWith(JUnit4.class) | |
| public final class SkyframeFocuserTest extends BuildViewTestCase { | |
| @Rule public final MockitoRule mockito = MockitoJUnit.rule(); | |
| @Mock private ActionCache mockActionCache; | |
| @Test | |
| public void testFocus_emptyInputsReturnsEmptyResult() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| FocusResult focusResult = | |
| SkyframeFocuser.focus(graph, mockActionCache, Sets.newHashSet(), Sets.newHashSet()); | |
| assertThat(focusResult.deps()).isEmpty(); | |
| assertThat(focusResult.rdeps()).isEmpty(); | |
| } | |
| @Test | |
| public void testFocus_keepsLeafs() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| createEdgesAndMarkDone(graph, cat, ImmutableList.of(), ImmutableList.of()); | |
| createEdgesAndMarkDone(graph, dog, ImmutableList.of(), ImmutableList.of()); | |
| Set<SkyKey> roots = Sets.newHashSet(); | |
| Set<SkyKey> leafs = Sets.newHashSet(cat, dog); | |
| FocusResult focusResult = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| assertThat(focusResult.deps()).isEmpty(); | |
| assertThat(focusResult.rdeps()).containsExactly(cat, dog); | |
| assertThat(graph.getValues().keySet()).containsExactly(cat, dog); | |
| } | |
| @Test | |
| public void testFocus_dropsUnreachableNodesFromLeafs() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| createEdgesAndMarkDone(graph, cat, ImmutableList.of(), ImmutableList.of()); | |
| createEdgesAndMarkDone(graph, dog, ImmutableList.of(), ImmutableList.of()); | |
| Set<SkyKey> roots = Sets.newHashSet(); | |
| Set<SkyKey> leafs = Sets.newHashSet(cat); // dog is unreachable | |
| FocusResult focusResult = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| assertThat(focusResult.deps()).isEmpty(); | |
| assertThat(focusResult.rdeps()).containsExactly(cat); | |
| assertThat(graph.getValues().keySet()).containsExactly(cat); | |
| } | |
| @Test | |
| public void testFocus_keepsReverseDepOfLeafs() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| createEdgesAndMarkDone(graph, cat, ImmutableList.of(), ImmutableList.of(dog)); | |
| createEdgesAndMarkDone(graph, dog, ImmutableList.of(), ImmutableList.of()); | |
| Set<SkyKey> roots = Sets.newHashSet(); | |
| Set<SkyKey> leafs = Sets.newHashSet(cat); // dog is cat's rdep | |
| FocusResult focusResult = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| assertThat(focusResult.deps()).isEmpty(); | |
| assertThat(focusResult.rdeps()).containsExactly(cat, dog); | |
| assertThat(graph.getValues().keySet()).containsExactly(cat, dog); | |
| } | |
| @Test | |
| public void testFocus_keepsRoots() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| createEdgesAndMarkDone(graph, cat, ImmutableList.of(), ImmutableList.of()); | |
| createEdgesAndMarkDone(graph, dog, ImmutableList.of(), ImmutableList.of()); | |
| Set<SkyKey> roots = Sets.newHashSet(cat, dog); | |
| Set<SkyKey> leafs = Sets.newHashSet(); | |
| FocusResult focusResult = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| assertThat(focusResult.deps()).containsExactly(cat, dog); | |
| assertThat(focusResult.rdeps()).isEmpty(); | |
| assertThat(graph.getValues().keySet()).containsExactly(cat, dog); | |
| } | |
| @Test | |
| public void testFocus_dropsUnreachableFromRoots() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| createEdgesAndMarkDone(graph, cat, ImmutableList.of(), ImmutableList.of()); | |
| createEdgesAndMarkDone(graph, dog, ImmutableList.of(), ImmutableList.of()); | |
| Set<SkyKey> roots = Sets.newHashSet(cat); | |
| Set<SkyKey> leafs = Sets.newHashSet(); | |
| FocusResult focusResult = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| assertThat(focusResult.deps()).containsExactly(cat); | |
| assertThat(focusResult.rdeps()).isEmpty(); | |
| assertThat(graph.getValues().keySet()).containsExactly(cat); | |
| } | |
| @Test | |
| public void testFocus_keepDirectDepsOfRdepTransitiveClosure() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| SkyKey civet = SkyKeyWithSkyKeyInterner.create("civet"); | |
| SkyKey hamster = SkyKeyWithSkyKeyInterner.create("hamster"); | |
| SkyKey fish = SkyKeyWithSkyKeyInterner.create("fish"); | |
| SkyKey bird = SkyKeyWithSkyKeyInterner.create("bird"); | |
| SkyKey monkey = SkyKeyWithSkyKeyInterner.create("monkey"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog, civet, hamster, fish, bird, monkey); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| // Graph: | |
| // | |
| // monkey (isolated) | |
| // | |
| // /-> fish -> bird | |
| // cat -> dog -> civet* | |
| // \-> hamster | |
| // | |
| // *Only civet in the working set. | |
| createEdgesAndMarkDone(graph, civet, ImmutableList.of(), ImmutableList.of(dog)); | |
| createEdgesAndMarkDone(graph, hamster, ImmutableList.of(), ImmutableList.of(dog)); | |
| createEdgesAndMarkDone(graph, dog, ImmutableList.of(civet, hamster), ImmutableList.of(cat)); | |
| createEdgesAndMarkDone(graph, bird, ImmutableList.of(), ImmutableList.of(fish)); | |
| createEdgesAndMarkDone(graph, fish, ImmutableList.of(bird), ImmutableList.of(cat)); | |
| createEdgesAndMarkDone(graph, cat, ImmutableList.of(dog, fish), ImmutableList.of()); | |
| createEdgesAndMarkDone(graph, monkey, ImmutableList.of(), ImmutableList.of()); | |
| Set<SkyKey> roots = Sets.newHashSet(cat); | |
| Set<SkyKey> leafs = Sets.newHashSet(civet); | |
| FocusResult focusResult = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| assertThat(focusResult.deps()).containsExactly(hamster, fish); | |
| assertThat(focusResult.rdeps()).containsExactly(civet, dog, cat); | |
| // no monkey (isolated) and bird (indirect dep) | |
| assertThat(graph.getValues().keySet()).containsExactly(hamster, fish, civet, dog, cat); | |
| } | |
| @Test | |
| public void testFocus_removeActionCacheEntries() throws InterruptedException { | |
| InMemoryGraph graph = skyframeExecutor.getEvaluator().getInMemoryGraph(); | |
| SkyKey cat = SkyKeyWithSkyKeyInterner.create("cat"); | |
| SkyKey dog = SkyKeyWithSkyKeyInterner.create("dog"); | |
| SkyKey hamster = SkyKeyWithSkyKeyInterner.create("hamster"); | |
| ImmutableList<SkyKey> keys = ImmutableList.of(cat, dog, hamster); | |
| graph.createIfAbsentBatch(null, Reason.OTHER, keys); | |
| ArtifactRoot artifactRoot = | |
| ArtifactRoot.asDerivedRoot( | |
| this.directories.getExecRoot("workspace"), RootType.Output, "blaze-out"); | |
| Action catAction = new NullAction(createArtifact(artifactRoot, "cat")); | |
| Action dogAction = new NullAction(createArtifact(artifactRoot, "dog")); | |
| Action hamsterAction = new NullAction(createArtifact(artifactRoot, "hamster")); | |
| createEdgesAndMarkDone( | |
| graph, | |
| cat, | |
| ImmutableList.of(), | |
| ImmutableList.of(), | |
| new BasicActionLookupValue(ImmutableList.of(catAction))); | |
| createEdgesAndMarkDone( | |
| graph, | |
| dog, | |
| ImmutableList.of(), | |
| ImmutableList.of(hamster), | |
| new BasicActionLookupValue(ImmutableList.of(dogAction))); | |
| createEdgesAndMarkDone( | |
| graph, | |
| hamster, | |
| ImmutableList.of(dog), | |
| ImmutableList.of(), | |
| new BasicActionLookupValue(ImmutableList.of(hamsterAction))); | |
| Set<SkyKey> roots = Sets.newHashSet(hamster); | |
| Set<SkyKey> leafs = Sets.newHashSet(dog); | |
| FocusResult unused = SkyframeFocuser.focus(graph, mockActionCache, roots, leafs); | |
| verify(mockActionCache).remove(catAction.getPrimaryOutput().getExecPathString()); | |
| verify(mockActionCache, never()).remove(dogAction.getPrimaryOutput().getExecPathString()); | |
| verify(mockActionCache, never()).remove(hamsterAction.getPrimaryOutput().getExecPathString()); | |
| } | |
| private static void createEdgesAndMarkDone( | |
| InMemoryGraph graph, SkyKey k, ImmutableList<SkyKey> deps, ImmutableList<SkyKey> rdeps) | |
| throws InterruptedException { | |
| createEdgesAndMarkDone(graph, k, deps, rdeps, new StringValue("unused")); | |
| } | |
| // Create dep and rdep edges for a node, and ensures that it's marked done. | |
| private static void createEdgesAndMarkDone( | |
| InMemoryGraph graph, | |
| SkyKey k, | |
| ImmutableList<SkyKey> deps, | |
| ImmutableList<SkyKey> rdeps, | |
| SkyValue value) | |
| throws InterruptedException { | |
| NodeEntry entry = graph.getIfPresent(k); | |
| assertThat(entry).isNotNull(); | |
| if (rdeps.isEmpty()) { | |
| entry.addReverseDepAndCheckIfDone(null); | |
| } else { | |
| for (SkyKey rdep : rdeps) { | |
| entry.addReverseDepAndCheckIfDone(rdep); | |
| } | |
| } | |
| entry.markRebuilding(); | |
| for (SkyKey dep : deps) { | |
| entry.addSingletonTemporaryDirectDep(dep); | |
| entry.signalDep(Version.constant(), dep); | |
| } | |
| entry.setValue(value, Version.constant(), null); | |
| } | |
| private static final class SkyKeyWithSkyKeyInterner extends AbstractSkyKey<String> { | |
| private static final SkyKeyInterner<SkyframeFocuserTest.SkyKeyWithSkyKeyInterner> interner = | |
| SkyKey.newInterner(); | |
| static SkyKeyWithSkyKeyInterner create(String arg) { | |
| return interner.intern(new SkyframeFocuserTest.SkyKeyWithSkyKeyInterner(arg)); | |
| } | |
| private SkyKeyWithSkyKeyInterner(String arg) { | |
| super(arg); | |
| } | |
| @Override | |
| public SkyFunctionName functionName() { | |
| return SkyFunctionName.FOR_TESTING; | |
| } | |
| @Override | |
| public SkyKeyInterner<SkyframeFocuserTest.SkyKeyWithSkyKeyInterner> getSkyKeyInterner() { | |
| return interner; | |
| } | |
| } | |
| } |