| // Copyright 2016 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.testutil.MoreAsserts.assertThrows; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.ActionLookupValue; |
| import com.google.devtools.build.lib.actions.ActionTemplate; |
| import com.google.devtools.build.lib.actions.Actions; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; |
| import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType; |
| import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; |
| import com.google.devtools.build.lib.actions.ArtifactOwner; |
| import com.google.devtools.build.lib.actions.ArtifactPrefixConflictException; |
| import com.google.devtools.build.lib.actions.ArtifactRoot; |
| import com.google.devtools.build.lib.actions.FileArtifactValue; |
| import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.actions.util.InjectedActionLookupKey; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; |
| import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate; |
| import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.OutputPathMapper; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.events.NullEventHandler; |
| import com.google.devtools.build.lib.packages.Package; |
| import com.google.devtools.build.lib.pkgcache.PathPackageLocator; |
| import com.google.devtools.build.lib.skyframe.ActionTemplateExpansionValue.ActionTemplateExpansionKey; |
| import com.google.devtools.build.lib.testutil.FoundationTestCase; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.devtools.build.skyframe.EvaluationContext; |
| import com.google.devtools.build.skyframe.EvaluationResult; |
| import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator; |
| import com.google.devtools.build.skyframe.MemoizingEvaluator; |
| import com.google.devtools.build.skyframe.SequencedRecordingDifferencer; |
| import com.google.devtools.build.skyframe.SequentialBuildDriver; |
| import com.google.devtools.build.skyframe.SkyFunction; |
| import com.google.devtools.build.skyframe.SkyFunctionName; |
| import com.google.devtools.build.skyframe.SkyKey; |
| import com.google.devtools.build.skyframe.SkyValue; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.UUID; |
| import java.util.concurrent.atomic.AtomicReference; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| import org.mockito.Mockito; |
| |
| /** Tests for {@link ActionTemplateExpansionFunction}. */ |
| @RunWith(JUnit4.class) |
| public final class ActionTemplateExpansionFunctionTest extends FoundationTestCase { |
| private Map<Artifact, TreeArtifactValue> artifactValueMap; |
| private SequentialBuildDriver driver; |
| private SequencedRecordingDifferencer differencer; |
| |
| @Before |
| public void setUp() throws Exception { |
| artifactValueMap = new LinkedHashMap<>(); |
| AtomicReference<PathPackageLocator> pkgLocator = |
| new AtomicReference<>( |
| new PathPackageLocator( |
| rootDirectory.getFileSystem().getPath("/outputbase"), |
| ImmutableList.of(Root.fromPath(rootDirectory)), |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY)); |
| differencer = new SequencedRecordingDifferencer(); |
| MemoizingEvaluator evaluator = |
| new InMemoryMemoizingEvaluator( |
| ImmutableMap.<SkyFunctionName, SkyFunction>builder() |
| .put(Artifact.ARTIFACT, new DummyArtifactFunction(artifactValueMap)) |
| .put( |
| SkyFunctions.ACTION_TEMPLATE_EXPANSION, |
| new ActionTemplateExpansionFunction(new ActionKeyContext())) |
| .build(), |
| differencer); |
| driver = new SequentialBuildDriver(evaluator); |
| PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID()); |
| PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get()); |
| } |
| |
| @Test |
| public void testActionTemplateExpansionFunction() throws Exception { |
| SpecialArtifact inputTreeArtifact = |
| createAndPopulateTreeArtifact("inputTreeArtifact", "child0", "child1", "child2"); |
| SpecialArtifact outputTreeArtifact = createTreeArtifact("outputTreeArtifact"); |
| |
| SpawnActionTemplate spawnActionTemplate = ActionsTestUtil.createDummySpawnActionTemplate( |
| inputTreeArtifact, outputTreeArtifact); |
| List<Action> actions = evaluate(spawnActionTemplate); |
| assertThat(actions).hasSize(3); |
| |
| ArtifactOwner owner = ActionTemplateExpansionValue.key(CTKEY, 0); |
| int i = 0; |
| for (Action action : actions) { |
| String childName = "child" + i; |
| assertThat(Artifact.asExecPaths(action.getInputs())) |
| .contains("out/inputTreeArtifact/" + childName); |
| assertThat(Artifact.asExecPaths(action.getOutputs())) |
| .containsExactly("out/outputTreeArtifact/" + childName); |
| assertThat(Iterables.getOnlyElement(action.getOutputs()).getArtifactOwner()).isEqualTo(owner); |
| ++i; |
| } |
| } |
| |
| @Test |
| public void testThrowsOnActionConflict() throws Exception { |
| SpecialArtifact inputTreeArtifact = |
| createAndPopulateTreeArtifact("inputTreeArtifact", "child0", "child1", "child2"); |
| SpecialArtifact outputTreeArtifact = createTreeArtifact("outputTreeArtifact"); |
| |
| OutputPathMapper mapper = new OutputPathMapper() { |
| @Override |
| public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) { |
| return PathFragment.create("conflict_path"); |
| } |
| }; |
| SpawnActionTemplate spawnActionTemplate = |
| new SpawnActionTemplate.Builder(inputTreeArtifact, outputTreeArtifact) |
| .setExecutable(PathFragment.create("/bin/cp")) |
| .setCommandLineTemplate(CustomCommandLine.builder().build()) |
| .setOutputPathMapper(mapper) |
| .build(ActionsTestUtil.NULL_ACTION_OWNER); |
| |
| assertThrows(ActionConflictException.class, () -> evaluate(spawnActionTemplate)); |
| } |
| |
| @Test |
| public void testThrowsOnArtifactPrefixConflict() throws Exception { |
| SpecialArtifact inputTreeArtifact = |
| createAndPopulateTreeArtifact("inputTreeArtifact", "child0", "child1", "child2"); |
| SpecialArtifact outputTreeArtifact = createTreeArtifact("outputTreeArtifact"); |
| |
| OutputPathMapper mapper = new OutputPathMapper() { |
| private int i = 0; |
| @Override |
| public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) { |
| PathFragment path; |
| switch (i) { |
| case 0: |
| path = PathFragment.create("path_prefix"); |
| break; |
| case 1: |
| path = PathFragment.create("path_prefix/conflict"); |
| break; |
| default: |
| path = inputTreeFileArtifact.getParentRelativePath(); |
| } |
| |
| ++i; |
| return path; |
| } |
| }; |
| SpawnActionTemplate spawnActionTemplate = |
| new SpawnActionTemplate.Builder(inputTreeArtifact, outputTreeArtifact) |
| .setExecutable(PathFragment.create("/bin/cp")) |
| .setCommandLineTemplate(CustomCommandLine.builder().build()) |
| .setOutputPathMapper(mapper) |
| .build(ActionsTestUtil.NULL_ACTION_OWNER); |
| |
| assertThrows(ArtifactPrefixConflictException.class, () -> evaluate(spawnActionTemplate)); |
| } |
| |
| private static final ActionLookupValue.ActionLookupKey CTKEY = new InjectedActionLookupKey("key"); |
| |
| private List<Action> evaluate(SpawnActionTemplate spawnActionTemplate) throws Exception { |
| ConfiguredTargetValue ctValue = createConfiguredTargetValue(spawnActionTemplate); |
| |
| differencer.inject(CTKEY, ctValue); |
| ActionTemplateExpansionKey templateKey = ActionTemplateExpansionValue.key(CTKEY, 0); |
| EvaluationContext evaluationContext = |
| EvaluationContext.newBuilder() |
| .setKeepGoing(false) |
| .setNumThreads(SkyframeExecutor.DEFAULT_THREAD_COUNT) |
| .setEventHander(NullEventHandler.INSTANCE) |
| .build(); |
| EvaluationResult<ActionTemplateExpansionValue> result = |
| driver.evaluate(ImmutableList.of(templateKey), evaluationContext); |
| if (result.hasError()) { |
| throw result.getError().getException(); |
| } |
| ActionTemplateExpansionValue actionTemplateExpansionValue = result.get(templateKey); |
| ImmutableList.Builder<Action> actionList = ImmutableList.builder(); |
| for (int i = 0; i < actionTemplateExpansionValue.getNumActions(); i++) { |
| actionList.add(actionTemplateExpansionValue.getAction(i)); |
| } |
| return actionList.build(); |
| } |
| |
| private static ConfiguredTargetValue createConfiguredTargetValue( |
| ActionTemplate<?> actionTemplate) { |
| return new NonRuleConfiguredTargetValue( |
| Mockito.mock(ConfiguredTarget.class), |
| Actions.GeneratingActions.fromSingleAction(actionTemplate, CTKEY), |
| NestedSetBuilder.<Package>stableOrder().build()); |
| } |
| |
| private SpecialArtifact createTreeArtifact(String path) { |
| PathFragment execPath = PathFragment.create("out").getRelative(path); |
| return new SpecialArtifact( |
| ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("out")), |
| execPath, |
| CTKEY, |
| SpecialArtifactType.TREE); |
| } |
| |
| private SpecialArtifact createAndPopulateTreeArtifact(String path, String... childRelativePaths) |
| throws Exception { |
| SpecialArtifact treeArtifact = createTreeArtifact(path); |
| Map<TreeFileArtifact, FileArtifactValue> treeFileArtifactMap = new LinkedHashMap<>(); |
| |
| for (String childRelativePath : childRelativePaths) { |
| TreeFileArtifact treeFileArtifact = |
| ActionsTestUtil.createTreeFileArtifactWithNoGeneratingAction( |
| treeArtifact, childRelativePath); |
| scratch.file(treeFileArtifact.getPath().toString(), childRelativePath); |
| // We do not care about the FileArtifactValues in this test. |
| treeFileArtifactMap.put( |
| treeFileArtifact, FileArtifactValue.createForTesting(treeFileArtifact)); |
| } |
| |
| artifactValueMap.put( |
| treeArtifact, TreeArtifactValue.create(ImmutableMap.copyOf(treeFileArtifactMap))); |
| |
| return treeArtifact; |
| } |
| |
| /** Dummy ArtifactFunction that just returns injected values */ |
| private static class DummyArtifactFunction implements SkyFunction { |
| private final Map<Artifact, TreeArtifactValue> artifactValueMap; |
| |
| DummyArtifactFunction(Map<Artifact, TreeArtifactValue> artifactValueMap) { |
| this.artifactValueMap = artifactValueMap; |
| } |
| @Override |
| public SkyValue compute(SkyKey skyKey, Environment env) { |
| return Preconditions.checkNotNull(artifactValueMap.get(skyKey)); |
| } |
| |
| @Override |
| public String extractTag(SkyKey skyKey) { |
| return null; |
| } |
| } |
| } |