|  | // Copyright 2019 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 com.github.benmanes.caffeine.cache.Cache; | 
|  | import com.github.benmanes.caffeine.cache.Caffeine; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import com.google.devtools.build.lib.actions.ActionKeyContext; | 
|  | import com.google.devtools.build.lib.actions.ActionLookupData; | 
|  | import com.google.devtools.build.lib.actions.Artifact; | 
|  | import com.google.devtools.build.lib.actions.util.TestAction; | 
|  | import com.google.devtools.build.lib.actionsketch.ActionSketch; | 
|  | import com.google.devtools.build.lib.collect.nestedset.NestedSet; | 
|  | import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; | 
|  | import com.google.devtools.build.lib.collect.nestedset.Order; | 
|  | import com.google.devtools.build.lib.util.Fingerprint; | 
|  | import com.google.devtools.build.lib.vfs.FileSystemUtils; | 
|  | import javax.annotation.Nullable; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.JUnit4; | 
|  |  | 
|  | /** Tests for top-down, transitive action caching. */ | 
|  | @RunWith(JUnit4.class) | 
|  | public final class TopDownActionCacheTest extends TimestampBuilderTestCase { | 
|  |  | 
|  | @Override | 
|  | protected TopDownActionCache initTopDownActionCache() { | 
|  | return new InMemoryTopDownActionCache(); | 
|  | } | 
|  |  | 
|  | private void buildArtifacts(Artifact... artifacts) throws Exception { | 
|  | buildArtifacts(amnesiacBuilder(), artifacts); | 
|  | } | 
|  |  | 
|  | private static NestedSet<Artifact> asNestedSet(Artifact... artifacts) { | 
|  | return NestedSetBuilder.create(Order.STABLE_ORDER, artifacts); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testAmnesiacBuilderGetsTopDownHit() throws Exception { | 
|  | Artifact hello = createDerivedArtifact("hello"); | 
|  | Button button = createActionButton(emptyNestedSet, ImmutableSet.of(hello)); | 
|  |  | 
|  | button.pressed = false; | 
|  | buildArtifacts(hello); | 
|  | assertThat(button.pressed).isTrue(); | 
|  |  | 
|  | button.pressed = false; | 
|  | buildArtifacts(hello); | 
|  | assertThat(button.pressed).isFalse(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testTransitiveTopDownCache() throws Exception { | 
|  | Artifact hello = createDerivedArtifact("hello"); | 
|  | Artifact hello2 = createDerivedArtifact("hello2"); | 
|  | Button button = createActionButton(emptyNestedSet, ImmutableSet.of(hello)); | 
|  | Button button2 = createActionButton(asNestedSet(hello), ImmutableSet.of(hello2)); | 
|  |  | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isTrue(); | 
|  | assertThat(button2.pressed).isTrue(); | 
|  |  | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isFalse(); | 
|  | assertThat(button2.pressed).isFalse(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testActionKeyCaching() throws Exception { | 
|  | Artifact hello = createDerivedArtifact("hello"); | 
|  | Artifact hello2 = createDerivedArtifact("hello2"); | 
|  |  | 
|  | ActionKeyButton button = createActionKeyButton(emptyNestedSet, ImmutableSet.of(hello), "abc"); | 
|  | ActionKeyButton button2 = | 
|  | createActionKeyButton(asNestedSet(hello), ImmutableSet.of(hello2), "xyz"); | 
|  |  | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isTrue(); | 
|  | assertThat(button2.pressed).isTrue(); | 
|  |  | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isFalse(); | 
|  | assertThat(button2.pressed).isFalse(); | 
|  |  | 
|  | clearActions(); | 
|  | hello = createDerivedArtifact("hello"); | 
|  | hello2 = createDerivedArtifact("hello2"); | 
|  | button = createActionKeyButton(emptyNestedSet, ImmutableSet.of(hello), "abc"); | 
|  | button2 = createActionKeyButton(asNestedSet(hello), ImmutableSet.of(hello2), "123"); | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isFalse(); | 
|  | assertThat(button2.pressed).isTrue(); | 
|  |  | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isFalse(); | 
|  | assertThat(button2.pressed).isFalse(); | 
|  |  | 
|  | clearActions(); | 
|  | hello = createDerivedArtifact("hello"); | 
|  | hello2 = createDerivedArtifact("hello2"); | 
|  | button = createActionKeyButton(emptyNestedSet, ImmutableSet.of(hello), "456"); | 
|  | button2 = createActionKeyButton(asNestedSet(hello), ImmutableSet.of(hello2), "123"); | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isTrue(); | 
|  | assertThat(button2.pressed).isTrue(); | 
|  |  | 
|  | button.pressed = false; | 
|  | button2.pressed = false; | 
|  | buildArtifacts(hello2); | 
|  | assertThat(button.pressed).isFalse(); | 
|  | assertThat(button2.pressed).isFalse(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testSingleSourceArtifactChanged() throws Exception { | 
|  | Artifact hello = createSourceArtifact("hello"); | 
|  | hello.getPath().getParentDirectory().createDirectoryAndParents(); | 
|  | FileSystemUtils.writeContentAsLatin1(hello.getPath(), "content1"); | 
|  |  | 
|  | Artifact goodbye = createDerivedArtifact("goodbye"); | 
|  | Button button = createActionButton(asNestedSet(hello), ImmutableSet.of(goodbye)); | 
|  |  | 
|  | button.pressed = false; | 
|  | buildArtifacts(goodbye); | 
|  | assertThat(button.pressed).isTrue(); | 
|  |  | 
|  | button.pressed = false; | 
|  | buildArtifacts(goodbye); | 
|  | assertThat(button.pressed).isFalse(); // top-down cached | 
|  |  | 
|  | FileSystemUtils.writeContentAsLatin1(hello.getPath(), "content1"); | 
|  | button.pressed = false; | 
|  | buildArtifacts(goodbye); | 
|  | assertThat(button.pressed).isFalse(); // top-down cached | 
|  |  | 
|  | FileSystemUtils.writeContentAsLatin1(hello.getPath(), "content2"); | 
|  | button.pressed = false; | 
|  | buildArtifacts(goodbye); | 
|  | assertThat(button.pressed).isTrue(); // built | 
|  |  | 
|  | button.pressed = false; | 
|  | buildArtifacts(goodbye); | 
|  | assertThat(button.pressed).isFalse(); // top-down cached | 
|  | } | 
|  |  | 
|  | private static class InMemoryTopDownActionCache implements TopDownActionCache { | 
|  | private final Cache<ActionSketch, ActionExecutionValue> cache = Caffeine.newBuilder().build(); | 
|  |  | 
|  | @Nullable | 
|  | @Override | 
|  | public ActionExecutionValue get(ActionSketch sketch, ActionLookupData action) { | 
|  | return cache.getIfPresent(sketch); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void put(ActionSketch sketch, ActionExecutionValue value) { | 
|  | cache.put(sketch, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final class MutableActionKeyAction extends TestAction { | 
|  | private final ActionKeyButton button; | 
|  |  | 
|  | MutableActionKeyAction( | 
|  | ActionKeyButton button, NestedSet<Artifact> inputs, ImmutableSet<Artifact> outputs) { | 
|  | super(button, inputs, outputs); | 
|  | this.button = button; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void computeKey( | 
|  | ActionKeyContext actionKeyContext, | 
|  | @Nullable Artifact.ArtifactExpander artifactExpander, | 
|  | Fingerprint fp) { | 
|  | super.computeKey(actionKeyContext, artifactExpander, fp); | 
|  | fp.addString(button.key); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final class ActionKeyButton extends Button { | 
|  | private final String key; | 
|  |  | 
|  | ActionKeyButton(String key) { | 
|  | this.key = key; | 
|  | } | 
|  | } | 
|  |  | 
|  | private ActionKeyButton createActionKeyButton( | 
|  | NestedSet<Artifact> inputs, ImmutableSet<Artifact> outputs, String key) { | 
|  | ActionKeyButton button = new ActionKeyButton(key); | 
|  | registerAction(new MutableActionKeyAction(button, inputs, outputs)); | 
|  | return button; | 
|  | } | 
|  | } |