blob: 5221fe2e57933393dcb3ee04f0cb164687357e70 [file] [log] [blame]
// Copyright 2020 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.starlark;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
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.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.Artifact.MissingExpansionException;
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.ArtifactRoot;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLine.ArgChunk;
import com.google.devtools.build.lib.actions.CommandLine.SimpleArgChunk;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
import com.google.devtools.build.lib.actions.HasDigest;
import com.google.devtools.build.lib.actions.PathMapper;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.starlark.StarlarkCustomCommandLine.VectorArg;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.Tuple;
import net.starlark.java.syntax.Location;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link StarlarkCustomCommandLine}. */
@RunWith(JUnit4.class)
public final class StarlarkCustomCommandLineTest {
private static final ArtifactExpander EMPTY_EXPANDER = artifact -> ImmutableSortedSet.of();
private final Scratch scratch = new Scratch();
private Path execRoot;
private ArtifactRoot derivedRoot;
private final StarlarkCustomCommandLine.Builder builder =
new StarlarkCustomCommandLine.Builder(StarlarkSemantics.DEFAULT);
@Before
public void createArtifactRoot() throws IOException {
execRoot = scratch.dir("execroot");
derivedRoot = ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "bin");
}
@Test
public void add() throws Exception {
CommandLine commandLine =
builder
.add("one")
.add("two")
.add("three")
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "one", "two", "three");
}
@Test
public void addFormatted() throws Exception {
CommandLine commandLine =
builder
.addFormatted("one", "--arg1=%s")
.addFormatted("two", "--arg2=%s")
.addFormatted("three", "--arg3=%s")
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "--arg1=one", "--arg2=two", "--arg3=three");
}
@Test
public void argName() throws Exception {
CommandLine commandLine =
builder
.add(vectorArg("one", "two", "three").setArgName("--arg"))
.add(vectorArg("four").setArgName("--other_arg"))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "--arg", "one", "two", "three", "--other_arg", "four");
}
@Test
public void terminateWith() throws Exception {
CommandLine commandLine =
builder
.add(vectorArg("one", "two", "three").setTerminateWith("end1"))
.add(vectorArg("four").setTerminateWith("end2"))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "one", "two", "three", "end1", "four", "end2");
}
@Test
public void formatEach() throws Exception {
CommandLine commandLine =
builder
.add(vectorArg("one", "two", "three").setFormatEach("--arg=%s"))
.add(vectorArg("four").setFormatEach("--other_arg=%s"))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "--arg=one", "--arg=two", "--arg=three", "--other_arg=four");
}
@Test
public void beforeEach() throws Exception {
CommandLine commandLine =
builder
.add(vectorArg("one", "two", "three").setBeforeEach("b4"))
.add(vectorArg("four").setBeforeEach("and"))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "b4", "one", "b4", "two", "b4", "three", "and", "four");
}
@Test
public void joinWith() throws Exception {
CommandLine commandLine =
builder
.add(vectorArg("one", "two", "three").setJoinWith("..."))
.add(vectorArg("four").setJoinWith("n/a"))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "one...two...three", "four");
}
@Test
public void formatJoined() throws Exception {
CommandLine commandLine =
builder
.add(vectorArg("one", "two", "three").setJoinWith("...").setFormatJoined("--arg=%s"))
.add(vectorArg("four").setJoinWith("n/a").setFormatJoined("--other_arg=%s"))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "--arg=one...two...three", "--other_arg=four");
}
@Test
public void emptyVectorArg_omit() throws Exception {
CommandLine commandLine =
builder
.add("before")
.add(vectorArg().omitIfEmpty(true).setJoinWith(",").setFormatJoined("--empty=%s"))
.add("after")
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "before", "after");
}
@Test
public void emptyVectorArg_noOmit() throws Exception {
CommandLine commandLine =
builder
.add("before")
.add(vectorArg().omitIfEmpty(false).setJoinWith(",").setFormatJoined("--empty=%s"))
.add("after")
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(commandLine, "before", "--empty=", "after");
}
@Test
public void flagPerLine() throws Exception {
CommandLine commandLine =
builder
.recordArgStart()
.add(vectorArg("is", "line", "one").setArgName("--this"))
.recordArgStart()
.add(vectorArg("this", "is", "line", "two").setArgName("--and"))
.recordArgStart()
.add("--line_three")
.add("single_arg")
.recordArgStart()
.add(vectorArg("", "line", "four", "has", "no").setTerminateWith("flag"))
.build(/* flagPerLine= */ true, RepositoryMapping.ALWAYS_FALLBACK);
verifyCommandLine(
commandLine,
"--this=is line one",
"--and=this is line two",
"--line_three=single_arg",
"line four has no flag");
}
@Test
public void vectorArgAddToFingerprint_treeArtifactMissingExpansion_returnsDigest()
throws Exception {
SpecialArtifact tree = createTreeArtifact("tree");
CommandLine commandLine =
builder
.add(vectorArg(tree).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
ActionKeyContext actionKeyContext = new ActionKeyContext();
Fingerprint fingerprint = new Fingerprint();
// TODO(b/167696101): Fail arguments computation when we are missing the directory from inputs.
commandLine.addToFingerprint(actionKeyContext, EMPTY_EXPANDER, fingerprint);
assertThat(fingerprint.digestAndReset()).isNotEmpty();
}
@Test
public void vectorArgAddToFingerprint_expandFileset_includesInDigest() throws Exception {
SpecialArtifact fileset = createFileset("fileset");
CommandLine commandLine =
builder
.add(vectorArg(fileset).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
FilesetOutputSymlink symlink1 = createFilesetSymlink("file1");
FilesetOutputSymlink symlink2 = createFilesetSymlink("file2");
ActionKeyContext actionKeyContext = new ActionKeyContext();
Fingerprint fingerprint = new Fingerprint();
ArtifactExpander artifactExpander =
createArtifactExpander(
/*treeExpansions=*/ ImmutableMap.of(),
ImmutableMap.of(fileset, ImmutableList.of(symlink1, symlink2)));
commandLine.addToFingerprint(actionKeyContext, artifactExpander, fingerprint);
assertThat(fingerprint.digestAndReset()).isNotEmpty();
}
@Test
public void vectorArgAddToFingerprint_expandTreeArtifact_includesInDigest() throws Exception {
SpecialArtifact tree = createTreeArtifact("tree");
CommandLine commandLine =
builder
.add(vectorArg(tree).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
TreeFileArtifact child = TreeFileArtifact.createTreeOutput(tree, "child");
ActionKeyContext actionKeyContext = new ActionKeyContext();
Fingerprint fingerprint = new Fingerprint();
ArtifactExpander artifactExpander =
createArtifactExpander(
ImmutableMap.of(tree, ImmutableSortedSet.of(child)),
/*filesetExpansions*/ ImmutableMap.of());
commandLine.addToFingerprint(actionKeyContext, artifactExpander, fingerprint);
assertThat(fingerprint.digestAndReset()).isNotEmpty();
}
@Test
public void vectorArgAddToFingerprint_expandFilesetMissingExpansion_fails() {
SpecialArtifact fileset = createFileset("fileset");
CommandLine commandLine =
builder
.add(vectorArg(fileset).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
ActionKeyContext actionKeyContext = new ActionKeyContext();
Fingerprint fingerprint = new Fingerprint();
assertThrows(
CommandLineExpansionException.class,
() -> commandLine.addToFingerprint(actionKeyContext, EMPTY_EXPANDER, fingerprint));
}
@Test
public void vectorArgArguments_expandsTreeArtifact() throws Exception {
SpecialArtifact tree = createTreeArtifact("tree");
CommandLine commandLine =
builder
.add(vectorArg(tree).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
TreeFileArtifact child1 = TreeFileArtifact.createTreeOutput(tree, "child1");
TreeFileArtifact child2 = TreeFileArtifact.createTreeOutput(tree, "child2");
ArtifactExpander artifactExpander =
createArtifactExpander(
ImmutableMap.of(tree, ImmutableSortedSet.of(child1, child2)),
/*filesetExpansions*/ ImmutableMap.of());
Iterable<String> arguments = commandLine.arguments(artifactExpander, PathMapper.NOOP);
assertThat(arguments).containsExactly("bin/tree/child1", "bin/tree/child2");
}
@Test
public void vectorArgArguments_expandsFileset() throws Exception {
SpecialArtifact fileset = createFileset("fileset");
CommandLine commandLine =
builder
.add(vectorArg(fileset).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
FilesetOutputSymlink symlink1 = createFilesetSymlink("file1");
FilesetOutputSymlink symlink2 = createFilesetSymlink("file2");
ArtifactExpander artifactExpander =
createArtifactExpander(
/*treeExpansions=*/ ImmutableMap.of(),
ImmutableMap.of(fileset, ImmutableList.of(symlink1, symlink2)));
Iterable<String> arguments = commandLine.arguments(artifactExpander, PathMapper.NOOP);
assertThat(arguments).containsExactly("bin/fileset/file1", "bin/fileset/file2");
}
@Test
public void vectorArgArguments_treeArtifactMissingExpansion_returnsEmptyList() throws Exception {
SpecialArtifact tree = createTreeArtifact("tree");
CommandLine commandLine =
builder
.add(vectorArg(tree).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
// TODO(b/167696101): Fail arguments computation when we are missing the directory from inputs.
assertThat(commandLine.arguments(EMPTY_EXPANDER, PathMapper.NOOP)).isEmpty();
}
@Test
public void vectorArgArguments_filesetMissingExpansion_fails() {
SpecialArtifact fileset = createFileset("fileset");
CommandLine commandLine =
builder
.add(vectorArg(fileset).setExpandDirectories(true))
.build(/* flagPerLine= */ false, RepositoryMapping.ALWAYS_FALLBACK);
assertThrows(
CommandLineExpansionException.class,
() -> commandLine.arguments(EMPTY_EXPANDER, PathMapper.NOOP));
}
private static VectorArg.Builder vectorArg(Object... elems) {
return new VectorArg.Builder(Tuple.of(elems)).setLocation(Location.BUILTIN);
}
private static void verifyCommandLine(CommandLine commandLine, String... expected)
throws CommandLineExpansionException, InterruptedException {
ArgChunk chunk = commandLine.expand(EMPTY_EXPANDER, PathMapper.NOOP);
assertThat(chunk.arguments()).containsExactlyElementsIn(expected).inOrder();
// Check consistency of the total argument length calculation with SimpleArgChunk, which
// materializes strings and adds up their lengths.
assertThat(chunk.totalArgLength())
.isEqualTo(new SimpleArgChunk(chunk.arguments()).totalArgLength());
}
private SpecialArtifact createFileset(String relativePath) {
return createSpecialArtifact(relativePath, SpecialArtifactType.FILESET);
}
private FilesetOutputSymlink createFilesetSymlink(String relativePath) {
return FilesetOutputSymlink.create(
PathFragment.create(relativePath),
PathFragment.EMPTY_FRAGMENT,
mock(HasDigest.class),
execRoot.asFragment());
}
private SpecialArtifact createTreeArtifact(String relativePath) {
SpecialArtifact tree = createSpecialArtifact(relativePath, SpecialArtifactType.TREE);
tree.setGeneratingActionKey(ActionLookupData.create(ActionsTestUtil.NULL_ARTIFACT_OWNER, 0));
return tree;
}
private SpecialArtifact createSpecialArtifact(String relativePath, SpecialArtifactType type) {
return SpecialArtifact.create(
derivedRoot,
derivedRoot.getExecPath().getRelative(relativePath),
ActionsTestUtil.NULL_ARTIFACT_OWNER,
type);
}
private static ArtifactExpander createArtifactExpander(
ImmutableMap<SpecialArtifact, ImmutableSortedSet<TreeFileArtifact>> treeExpansions,
ImmutableMap<SpecialArtifact, ImmutableList<FilesetOutputSymlink>> filesetExpansions) {
return new ArtifactExpander() {
@Override
public ImmutableSortedSet<TreeFileArtifact> expandTreeArtifact(Artifact treeArtifact) {
//noinspection SuspiciousMethodCalls
return treeExpansions.getOrDefault(treeArtifact, ImmutableSortedSet.of());
}
@Override
public ImmutableList<FilesetOutputSymlink> expandFileset(Artifact artifact)
throws MissingExpansionException {
//noinspection SuspiciousMethodCalls
ImmutableList<FilesetOutputSymlink> filesetLinks = filesetExpansions.get(artifact);
if (filesetLinks == null) {
throw new MissingExpansionException("Cannot expand " + artifact);
}
return filesetLinks;
}
};
}
}