| // Copyright 2015 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.analysis.actions; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| 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.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ArtifactRoot; |
| import com.google.devtools.build.lib.actions.ArtifactRoot.RootType; |
| import com.google.devtools.build.lib.actions.Executor; |
| import com.google.devtools.build.lib.actions.ThreadStateReceiver; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.analysis.BlazeDirectories; |
| import com.google.devtools.build.lib.analysis.ServerDirectories; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetExpander; |
| import com.google.devtools.build.lib.events.StoredEventHandler; |
| import com.google.devtools.build.lib.exec.BinTools; |
| import com.google.devtools.build.lib.exec.util.TestExecutorBuilder; |
| import com.google.devtools.build.lib.testutil.FoundationTestCase; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import com.google.devtools.build.lib.util.io.FileOutErr; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.devtools.build.lib.vfs.UnixGlob; |
| import java.nio.charset.StandardCharsets; |
| import java.util.List; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Tests {@link TemplateExpansionAction}. |
| */ |
| @RunWith(JUnit4.class) |
| public class TemplateExpansionActionTest extends FoundationTestCase { |
| |
| private static final String TEMPLATE = Joiner.on('\n').join("key=%key%", "value=%value%"); |
| private static final String SPECIAL_CHARS = "Š©±½_strøget"; |
| |
| private ArtifactRoot outputRoot; |
| private Artifact inputArtifact; |
| private Artifact outputArtifact; |
| private Path output; |
| private List<Substitution> substitutions; |
| private BlazeDirectories directories; |
| private BinTools binTools; |
| private final ActionKeyContext actionKeyContext = new ActionKeyContext(); |
| |
| @Before |
| public final void createDirectoriesAndTools() throws Exception { |
| createArtifacts(TEMPLATE); |
| |
| substitutions = Lists.newArrayList(); |
| substitutions.add(Substitution.of("%key%", "foo")); |
| substitutions.add(Substitution.of("%value%", "bar")); |
| directories = |
| new BlazeDirectories( |
| new ServerDirectories( |
| scratch.resolve("/install"), |
| scratch.resolve("/base"), |
| scratch.resolve("/userRoot")), |
| scratch.resolve("/workspace"), |
| /* defaultSystemJavabase= */ null, |
| "mock-product-name"); |
| binTools = BinTools.empty(directories); |
| } |
| |
| private void createArtifacts(String template) throws Exception { |
| ArtifactRoot workspace = ArtifactRoot.asSourceRoot(Root.fromPath(scratch.dir("/workspace"))); |
| scratch.dir("/workspace/out"); |
| outputRoot = ArtifactRoot.asDerivedRoot(scratch.dir("/workspace"), RootType.Output, "out"); |
| Path input = scratch.overwriteFile("/workspace/input.txt", StandardCharsets.UTF_8, template); |
| inputArtifact = ActionsTestUtil.createArtifact(workspace, input); |
| output = scratch.resolve("/workspace/out/destination.txt"); |
| outputArtifact = ActionsTestUtil.createArtifact(outputRoot, output); |
| } |
| |
| private TemplateExpansionAction create() { |
| TemplateExpansionAction result = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact, Template.forString(TEMPLATE), substitutions, false); |
| return result; |
| } |
| |
| @Test |
| public void testInputsIsEmpty() { |
| assertThat(create().getInputs().toList()).isEmpty(); |
| } |
| |
| @Test |
| public void testDestinationArtifactIsOutput() { |
| assertThat(create().getOutputs()).containsExactly(outputArtifact); |
| } |
| |
| @Test |
| public void testExpansion() throws Exception { |
| Executor executor = new TestExecutorBuilder(fileSystem, directories, binTools).build(); |
| create().execute(createContext(executor)); |
| String content = new String(FileSystemUtils.readContentAsLatin1(output)); |
| String expected = Joiner.on('\n').join("key=foo", "value=bar"); |
| assertThat(content).isEqualTo(expected); |
| } |
| |
| @Test |
| public void testKeySameIfSame() throws Exception { |
| Artifact outputArtifact2 = |
| ActionsTestUtil.createArtifact( |
| outputRoot, scratch.resolve("/workspace/out/destination.txt")); |
| TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo")), false); |
| TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact2, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo")), false); |
| |
| assertThat(computeKey(a)).isEqualTo(computeKey(b)); |
| } |
| |
| @Test |
| public void testKeyDiffersForSubstitution() throws Exception { |
| Artifact outputArtifact2 = |
| ActionsTestUtil.createArtifact( |
| outputRoot, scratch.resolve("/workspace/out/destination.txt")); |
| TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo")), false); |
| TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact2, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo2")), false); |
| |
| assertThat(computeKey(a)).isNotEqualTo(computeKey(b)); |
| } |
| |
| @Test |
| public void testKeyDiffersForExecutable() throws Exception { |
| Artifact outputArtifact2 = |
| ActionsTestUtil.createArtifact( |
| outputRoot, scratch.resolve("/workspace/out/destination.txt")); |
| TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo")), false); |
| TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact2, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo")), true); |
| |
| assertThat(computeKey(a)).isNotEqualTo(computeKey(b)); |
| } |
| |
| @Test |
| public void testKeyDiffersForTemplates() throws Exception { |
| Artifact outputArtifact2 = |
| ActionsTestUtil.createArtifact( |
| outputRoot, scratch.resolve("/workspace/out/destination.txt")); |
| TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact, Template.forString(TEMPLATE), |
| ImmutableList.of(Substitution.of("%key%", "foo")), false); |
| TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER, |
| outputArtifact2, Template.forString(TEMPLATE + " "), |
| ImmutableList.of(Substitution.of("%key%", "foo")), false); |
| |
| assertThat(computeKey(a)).isNotEqualTo(computeKey(b)); |
| } |
| |
| private TemplateExpansionAction createWithArtifact() { |
| return createWithArtifact(substitutions); |
| } |
| |
| private TemplateExpansionAction createWithArtifact(List<Substitution> substitutions) { |
| TemplateExpansionAction result = new TemplateExpansionAction( |
| NULL_ACTION_OWNER, inputArtifact, outputArtifact, substitutions, false); |
| return result; |
| } |
| |
| private ActionExecutionContext createContext(Executor executor) { |
| return new ActionExecutionContext( |
| executor, |
| /*actionInputFileCache=*/ null, |
| ActionInputPrefetcher.NONE, |
| actionKeyContext, |
| /*metadataHandler=*/ null, |
| /*rewindingEnabled=*/ false, |
| LostInputsCheck.NONE, |
| new FileOutErr(), |
| new StoredEventHandler(), |
| /*clientEnv=*/ ImmutableMap.of(), |
| /*topLevelFilesets=*/ ImmutableMap.of(), |
| /*artifactExpander=*/ null, |
| /*actionFileSystem=*/ null, |
| /*skyframeDepsResult=*/ null, |
| NestedSetExpander.DEFAULT, |
| UnixGlob.DEFAULT_SYSCALLS, |
| ThreadStateReceiver.NULL_INSTANCE); |
| } |
| |
| private void executeTemplateExpansion(String expected) throws Exception { |
| executeTemplateExpansion(expected, substitutions); |
| } |
| |
| private void executeTemplateExpansion(String expected, List<Substitution> substitutions) |
| throws Exception { |
| Executor executor = new TestExecutorBuilder(fileSystem, directories, binTools).build(); |
| createWithArtifact(substitutions).execute(createContext(executor)); |
| String actual = FileSystemUtils.readContent(output, StandardCharsets.UTF_8); |
| assertThat(actual).isEqualTo(expected); |
| } |
| |
| @Test |
| public void testArtifactTemplateHasInput() { |
| assertThat(createWithArtifact().getInputs().toList()).containsExactly(inputArtifact); |
| } |
| |
| @Test |
| public void testArtifactTemplateHasOutput() { |
| assertThat(createWithArtifact().getOutputs()).containsExactly(outputArtifact); |
| } |
| |
| @Test |
| public void testArtifactTemplateExpansion() throws Exception { |
| // The trailing "" is needed because scratch.overwriteFile implicitly appends "\n". |
| String expected = Joiner.on('\n').join("key=foo", "value=bar", ""); |
| executeTemplateExpansion(expected); |
| } |
| |
| @Test |
| public void testWithSpecialCharacters() throws Exception { |
| // We have to overwrite the artifacts since we need our template in "inputs" |
| createArtifacts(SPECIAL_CHARS + "%key%"); |
| |
| // scratch.overwriteFile appends a newline, so we need an additional \n here |
| String expected = String.format("%s%s\n", SPECIAL_CHARS, SPECIAL_CHARS); |
| |
| executeTemplateExpansion(expected, ImmutableList.of(Substitution.of("%key%", SPECIAL_CHARS))); |
| } |
| |
| private String computeKey(TemplateExpansionAction action) { |
| Fingerprint fp = new Fingerprint(); |
| action.computeKey(actionKeyContext, /*artifactExpander=*/ null, fp); |
| return fp.hexDigestAndReset(); |
| } |
| } |