// 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.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.Executor; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.ServerDirectories; 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 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 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"))); outputRoot = ArtifactRoot.asDerivedRoot(scratch.dir("/workspace"), scratch.dir("/workspace/out")); Path input = scratch.overwriteFile("/workspace/input.txt", StandardCharsets.UTF_8, template); inputArtifact = new Artifact(input, workspace); output = scratch.resolve("/workspace/out/destination.txt"); outputArtifact = new Artifact(output, outputRoot); } 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()).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 = new Artifact(scratch.resolve("/workspace/out/destination.txt"), outputRoot); 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 = new Artifact(scratch.resolve("/workspace/out/destination.txt"), outputRoot); 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 = new Artifact(scratch.resolve("/workspace/out/destination.txt"), outputRoot); 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 = new Artifact(scratch.resolve("/workspace/out/destination.txt"), outputRoot); 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 substitutions) { TemplateExpansionAction result = new TemplateExpansionAction( NULL_ACTION_OWNER, inputArtifact, outputArtifact, substitutions, false); return result; } private ActionExecutionContext createContext(Executor executor) { return new ActionExecutionContext( executor, null, ActionInputPrefetcher.NONE, actionKeyContext, null, new FileOutErr(), ImmutableMap.of(), ImmutableMap.of(), null, /*actionFileSystem=*/ null, /*skyframeDepsResult=*/ null); } private void executeTemplateExpansion(String expected) throws Exception { executeTemplateExpansion(expected, substitutions); } private void executeTemplateExpansion(String expected, List 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()).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, fp); return fp.hexDigestAndReset(); } }