| // Copyright 2021 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.starlark; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext.LostInputsCheck; |
| import com.google.devtools.build.lib.actions.ActionInputPrefetcher; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.DiscoveredModulesPruner; |
| import com.google.devtools.build.lib.actions.Executor; |
| import com.google.devtools.build.lib.actions.ThreadStateReceiver; |
| import com.google.devtools.build.lib.analysis.actions.StarlarkAction; |
| import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| 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.exec.BinTools; |
| import com.google.devtools.build.lib.exec.util.TestExecutorBuilder; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.SyscallCache; |
| import java.util.LinkedHashMap; |
| import java.util.Optional; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| import org.mockito.ArgumentMatchers; |
| |
| /** Tests for {@link StarlarkAction} using the shadowed action parameter. */ |
| @RunWith(JUnit4.class) |
| public final class StarlarkActionWithShadowedActionTest extends BuildViewTestCase { |
| |
| private ActionExecutionContext executionContext; |
| private AnalysisTestUtil.CollectingAnalysisEnvironment collectingAnalysisEnvironment; |
| private NestedSet<Artifact> starlarkActionInputs; |
| private NestedSet<Artifact> shadowedActionInputs; |
| private NestedSet<Artifact> discoveredInputs; |
| private ImmutableMap<String, String> starlarkActionEnvironment; |
| private ImmutableMap<String, String> shadowedActionEnvironment; |
| |
| private Artifact output; |
| private PathFragment executable; |
| |
| @Before |
| public void createArtifacts() throws Exception { |
| collectingAnalysisEnvironment = |
| new AnalysisTestUtil.CollectingAnalysisEnvironment(getTestAnalysisEnvironment()); |
| starlarkActionInputs = |
| NestedSetBuilder.create( |
| Order.STABLE_ORDER, |
| getSourceArtifact("pkg/shadowed_action_inp1"), |
| getSourceArtifact("pkg/discovered_inp2"), |
| getSourceArtifact("pkg/starlark_action_inp3")); |
| shadowedActionInputs = |
| NestedSetBuilder.create( |
| Order.STABLE_ORDER, |
| getSourceArtifact("pkg/shadowed_action_inp1"), |
| getSourceArtifact("pkg/shadowed_action_inp2"), |
| getSourceArtifact("pkg/shadowed_action_inp3")); |
| discoveredInputs = |
| NestedSetBuilder.create( |
| Order.STABLE_ORDER, |
| getSourceArtifact("pkg/shadowed_action_inp1"), |
| getSourceArtifact("pkg/discovered_inp2"), |
| getSourceArtifact("pkg/discovered_inp3")); |
| output = getBinArtifactWithNoOwner("output"); |
| executable = scratch.file("/bin/xxx").asFragment(); |
| starlarkActionEnvironment = |
| ImmutableMap.of( |
| "repeated_var", "starlark_val", |
| "a_var", "a_val", |
| "b_var", "b_val"); |
| shadowedActionEnvironment = |
| ImmutableMap.of( |
| "repeated_var", "shadowed_val", |
| "c_var", "c_val", |
| "d_var", "d_val"); |
| } |
| |
| @Before |
| public void createExecutorAndContext() throws Exception { |
| BinTools binTools = BinTools.forUnitTesting(directories, analysisMock.getEmbeddedTools()); |
| Executor executor = new TestExecutorBuilder(fileSystem, directories, binTools).build(); |
| executionContext = |
| new ActionExecutionContext( |
| executor, |
| /* inputMetadataProvider= */ null, |
| ActionInputPrefetcher.NONE, |
| actionKeyContext, |
| /* outputMetadataStore= */ null, |
| /* rewindingEnabled= */ false, |
| LostInputsCheck.NONE, |
| /* fileOutErr= */ null, |
| /* eventHandler= */ null, |
| /* clientEnv= */ ImmutableMap.of(), |
| /* topLevelFilesets= */ ImmutableMap.of(), |
| /* artifactExpander= */ null, |
| /* actionFileSystem= */ null, |
| /* skyframeDepsResult= */ null, |
| DiscoveredModulesPruner.DEFAULT, |
| SyscallCache.NO_CACHE, |
| ThreadStateReceiver.NULL_INSTANCE); |
| } |
| |
| @Test |
| public void testUsingOnlyShadowedActionInputs() throws Exception { |
| // If both starlark action and the shadowed action do not have inputs, then getInputs of both of |
| // them should return empty set |
| Action shadowedAction = |
| createShadowedAction( |
| NestedSetBuilder.emptySet(Order.STABLE_ORDER), /*discoversInputs=*/ false, null); |
| StarlarkAction starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()).isEmpty(); |
| assertThat(starlarkAction.discoversInputs()).isFalse(); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.getAllowedDerivedInputs().toList()).isEmpty(); |
| |
| // If the starlark action does not have any inputs, then it will use the shadowed action inputs |
| shadowedAction = createShadowedAction(shadowedActionInputs, false, null); |
| starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn(shadowedActionInputs.toList()); |
| assertThat(starlarkAction.discoversInputs()).isFalse(); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.getAllowedDerivedInputs().toList()) |
| .containsExactlyElementsIn(shadowedActionInputs.toList()); |
| } |
| |
| @Test |
| public void testUsingOnlyShadowedActionWithDiscoveredInputs() throws Exception { |
| // Test that the shadowed action's discovered inputs are passed to the starlark action |
| Action shadowedAction = |
| createShadowedAction( |
| NestedSetBuilder.emptySet(Order.STABLE_ORDER), |
| /*discoversInputs=*/ true, |
| discoveredInputs); |
| StarlarkAction starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()).isEmpty(); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.getAllowedDerivedInputs().toList()).isEmpty(); |
| assertThat(starlarkAction.discoversInputs()).isTrue(); |
| assertThat(starlarkAction.discoverInputs(executionContext).toList()) |
| .containsExactlyElementsIn(discoveredInputs.toList()); |
| // after discovering inputs, the starlark action inputs should be updated |
| assertThat(starlarkAction.inputsKnown()).isTrue(); |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn(discoveredInputs.toList()); |
| |
| // Test that both inputs and discovered inputs of the shadowed action are passed to the starlark |
| // action |
| shadowedAction = createShadowedAction(shadowedActionInputs, true, discoveredInputs); |
| starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn(shadowedActionInputs.toList()); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.getAllowedDerivedInputs().toList()) |
| .containsExactlyElementsIn(shadowedActionInputs.toList()); |
| assertThat(starlarkAction.discoversInputs()).isTrue(); |
| assertThat(starlarkAction.discoverInputs(executionContext).toList()) |
| .containsExactlyElementsIn( |
| Sets.difference(discoveredInputs.toSet(), shadowedActionInputs.toSet())); |
| // after discovering inputs, the starlark action inputs should be updated |
| assertThat(starlarkAction.inputsKnown()).isTrue(); |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn( |
| Sets.union(shadowedActionInputs.toSet(), discoveredInputs.toSet())); |
| } |
| |
| @Test |
| public void testUsingShadowedActionWithStarlarkActionInputs() throws Exception { |
| // Test using Starlark action's inputs without using a shadowed action |
| StarlarkAction starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setExecutable(executable) |
| .addInput(starlarkActionInputs.toList().get(0)) |
| .addInput(starlarkActionInputs.toList().get(1)) |
| .addInput(starlarkActionInputs.toList().get(2)) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn(starlarkActionInputs.toList()); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.discoversInputs()).isFalse(); |
| |
| // Test using Starlark actions's inputs with shadowed action's inputs |
| Action shadowedAction = |
| createShadowedAction( |
| shadowedActionInputs, /*discoversInputs=*/ false, /*discoveredInputs=*/ null); |
| starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addInput(starlarkActionInputs.toList().get(0)) |
| .addInput(starlarkActionInputs.toList().get(1)) |
| .addInput(starlarkActionInputs.toList().get(2)) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn( |
| Sets.union(shadowedActionInputs.toSet(), starlarkActionInputs.toSet())); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.getAllowedDerivedInputs().toList()) |
| .containsExactlyElementsIn( |
| Sets.union(shadowedActionInputs.toSet(), starlarkActionInputs.toSet())); |
| assertThat(starlarkAction.discoversInputs()).isFalse(); |
| |
| // Test using Starlark actions's inputs with shadowed action's inputs and discovered inputs |
| shadowedAction = createShadowedAction(shadowedActionInputs, true, discoveredInputs); |
| starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addInput(starlarkActionInputs.toList().get(0)) |
| .addInput(starlarkActionInputs.toList().get(1)) |
| .addInput(starlarkActionInputs.toList().get(2)) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn( |
| Sets.union(shadowedActionInputs.toSet(), starlarkActionInputs.toSet())); |
| assertThat(starlarkAction.getUnusedInputsList()).isEmpty(); |
| assertThat(starlarkAction.getAllowedDerivedInputs().toList()) |
| .containsExactlyElementsIn( |
| Sets.union(shadowedActionInputs.toSet(), starlarkActionInputs.toSet())); |
| assertThat(starlarkAction.discoversInputs()).isTrue(); |
| assertThat(starlarkAction.discoverInputs(executionContext).toList()) |
| .containsExactly(discoveredInputs.toList().get(2)); |
| // after discovering inputs, the starlark action inputs should be updated |
| assertThat(starlarkAction.inputsKnown()).isTrue(); |
| assertThat(starlarkAction.getInputs().toList()) |
| .containsExactlyElementsIn( |
| Sets.union( |
| NestedSetBuilder.wrap( |
| Order.STABLE_ORDER, |
| Sets.union(shadowedActionInputs.toSet(), starlarkActionInputs.toSet())) |
| .toSet(), |
| discoveredInputs.toSet())); |
| } |
| |
| @Test |
| public void testPassingShadowedActionEnvironment() throws Exception { |
| // Test using Starlark action's environment without using a shadowed action |
| StarlarkAction starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setExecutable(executable) |
| .addInput(starlarkActionInputs.toList().get(0)) |
| .addOutput(output) |
| .setEnvironment(starlarkActionEnvironment) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getEffectiveEnvironment(ImmutableMap.of())) |
| .containsExactlyEntriesIn(starlarkActionEnvironment); |
| |
| // Test using shadowed action's environment without Starlark actions's environment |
| Action shadowedAction = |
| createShadowedAction( |
| shadowedActionInputs, /*discoversInputs=*/ false, /*discoveredInputs=*/ null); |
| starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addInput(starlarkActionInputs.toList().get(0)) |
| .addOutput(output) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| assertThat(starlarkAction.getEffectiveEnvironment(ImmutableMap.of())) |
| .containsExactlyEntriesIn(shadowedActionEnvironment); |
| |
| // Test using Starlark actions's environment with shadowed action's environment |
| starlarkAction = |
| (StarlarkAction) |
| new StarlarkAction.Builder() |
| .setShadowedAction(Optional.of(shadowedAction)) |
| .setExecutable(executable) |
| .addInput(starlarkActionInputs.toList().get(0)) |
| .addOutput(output) |
| .setEnvironment(starlarkActionEnvironment) |
| .build(NULL_ACTION_OWNER, targetConfig); |
| collectingAnalysisEnvironment.registerAction(starlarkAction); |
| |
| LinkedHashMap<String, String> expectedEnvironment = new LinkedHashMap<>(); |
| expectedEnvironment.putAll(shadowedActionEnvironment); |
| expectedEnvironment.putAll(starlarkActionEnvironment); |
| |
| ImmutableMap<String, String> actualEnvironment = |
| starlarkAction.getEffectiveEnvironment(ImmutableMap.of()); |
| assertThat(actualEnvironment).hasSize(5); |
| // Starlark action's env overwrites any repeated variable from the shadowed action env |
| assertThat(actualEnvironment).containsEntry("repeated_var", "starlark_val"); |
| assertThat(actualEnvironment).containsExactlyEntriesIn(expectedEnvironment); |
| } |
| |
| private Action createShadowedAction( |
| NestedSet<Artifact> inputs, boolean discoversInputs, NestedSet<Artifact> discoveredInputs) |
| throws Exception { |
| Action shadowedAction = mock(Action.class); |
| when(shadowedAction.discoversInputs()).thenReturn(discoversInputs); |
| when(shadowedAction.getInputs()).thenReturn(inputs); |
| when(shadowedAction.getAllowedDerivedInputs()).thenReturn(inputs); |
| when(shadowedAction.getInputFilesForExtraAction( |
| ArgumentMatchers.any(ActionExecutionContext.class))) |
| .thenReturn(discoveredInputs); |
| when(shadowedAction.inputsKnown()).thenReturn(true); |
| when(shadowedAction.getOwner()).thenReturn(NULL_ACTION_OWNER); |
| when(shadowedAction.getEffectiveEnvironment(ArgumentMatchers.anyMap())) |
| .thenReturn(ImmutableMap.copyOf(shadowedActionEnvironment)); |
| |
| return shadowedAction; |
| } |
| } |