blob: b4a782195b36051b576e51d32e813072aba54004 [file] [log] [blame]
// Copyright 2023 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.exec;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.exec.SpawnLogContext.millisToProto;
import static com.google.devtools.build.lib.testutil.TestConstants.PRODUCT_NAME;
import static com.google.devtools.build.lib.testutil.TestConstants.WORKSPACE_NAME;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
import com.google.common.base.Utf8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.devtools.build.lib.actions.ActionEnvironment;
import com.google.devtools.build.lib.actions.ActionInput;
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.ArtifactExpander;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.actions.FilesetOutputTree;
import com.google.devtools.build.lib.actions.InputMetadataProvider;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.actions.PathMapper;
import com.google.devtools.build.lib.actions.RunfilesTree;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnMetrics;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.SpawnResult.Status;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesSupport;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.FragmentFactory;
import com.google.devtools.build.lib.analysis.config.FragmentRegistry;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyBuiltins;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
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.Protos.Digest;
import com.google.devtools.build.lib.exec.Protos.EnvironmentVariable;
import com.google.devtools.build.lib.exec.Protos.File;
import com.google.devtools.build.lib.exec.Protos.Platform;
import com.google.devtools.build.lib.exec.Protos.SpawnExec;
import com.google.devtools.build.lib.exec.util.FakeActionInputFileCache;
import com.google.devtools.build.lib.exec.util.SpawnBuilder;
import com.google.devtools.build.lib.server.FailureDetails.Crash;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
import com.google.devtools.build.lib.testutil.TestConstants;
import com.google.devtools.build.lib.vfs.DelegateFileSystem;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.protobuf.util.Timestamps;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Base class for {@link SpawnLogContext} tests. */
@RunWith(TestParameterInjector.class)
public abstract class SpawnLogContextTestBase {
protected final DigestHashFunction digestHashFunction = DigestHashFunction.SHA256;
protected final FileSystem fs = new InMemoryFileSystem(digestHashFunction);
protected final Path outputBase = fs.getPath("/home/user/bazel/output_base");
protected final Path externalRoot =
outputBase.getRelative(LabelConstants.EXTERNAL_REPOSITORY_LOCATION);
protected final RepositoryName externalRepo = RepositoryName.createUnvalidated("some_repo");
protected ArtifactRoot outputDir;
protected Path execRoot;
protected ArtifactRoot rootDir;
protected ArtifactRoot externalSourceRoot;
protected ArtifactRoot externalOutputDir;
protected BuildConfigurationValue configuration;
@TestParameter public boolean siblingRepositoryLayout;
@Before
public void setup() throws InvalidConfigurationException, OptionsParsingException {
BuildOptions defaultBuildOptions = BuildOptions.of(ImmutableList.of(CoreOptions.class));
configuration =
BuildConfigurationValue.createForTesting(
defaultBuildOptions,
"k8-fastbuild",
WORKSPACE_NAME,
siblingRepositoryLayout,
new BlazeDirectories(
new ServerDirectories(outputBase, outputBase, outputBase),
/* workspace= */ null,
/* defaultSystemJavabase= */ null,
TestConstants.PRODUCT_NAME),
new BuildConfigurationValue.GlobalStateProvider() {
@Override
public ActionEnvironment getActionEnvironment(BuildOptions buildOptions) {
return ActionEnvironment.EMPTY;
}
@Override
public FragmentRegistry getFragmentRegistry() {
return FragmentRegistry.create(
ImmutableList.of(), ImmutableList.of(), ImmutableList.of());
}
@Override
public ImmutableSet<String> getReservedActionMnemonics() {
return ImmutableSet.of();
}
},
new FragmentFactory());
outputDir = configuration.getBinDirectory(RepositoryName.MAIN);
execRoot = configuration.getDirectories().getExecRoot(WORKSPACE_NAME);
rootDir = ArtifactRoot.asSourceRoot(Root.fromPath(execRoot));
externalSourceRoot =
ArtifactRoot.asExternalSourceRoot(
Root.fromPath(externalRoot.getChild(externalRepo.getName())));
externalOutputDir = configuration.getBinDirectory(externalRepo);
}
// A fake action filesystem that provides a fast digest, but refuses to compute it from the
// file contents (which won't be available when building without the bytes).
protected static final class FakeActionFileSystem extends DelegateFileSystem {
FakeActionFileSystem(FileSystem delegateFs) {
super(delegateFs);
}
@Override
protected byte[] getFastDigest(PathFragment path) throws IOException {
return super.getDigest(path);
}
@Override
protected byte[] getDigest(PathFragment path) throws IOException {
throw new UnsupportedOperationException();
}
}
/** Test parameter determining whether the spawn inputs are also tool inputs. */
protected enum InputsMode {
TOOLS,
NON_TOOLS;
boolean isTool() {
return this == TOOLS;
}
}
/** Test parameter determining whether to emulate building with or without the bytes. */
protected enum OutputsMode {
WITH_BYTES,
WITHOUT_BYTES;
FileSystem getActionFileSystem(FileSystem fs) {
return this == WITHOUT_BYTES ? new FakeActionFileSystem(fs) : fs;
}
}
/** Test parameter determining whether an input/output directory should be empty. */
enum DirContents {
EMPTY,
NON_EMPTY;
boolean isEmpty() {
return this == EMPTY;
}
}
/** Test parameter determining whether an output is indirected through a symlink. */
enum OutputIndirection {
DIRECT,
INDIRECT;
boolean viaSymlink() {
return this == INDIRECT;
}
}
@Test
public void testFileInput(@TestParameter InputsMode inputsMode) throws Exception {
Artifact fileInput = ActionsTestUtil.createArtifact(rootDir, "file");
writeFile(fileInput, "abc");
SpawnBuilder spawn = defaultSpawnBuilder().withInputs(fileInput);
if (inputsMode.isTool()) {
spawn.withTools(fileInput);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(fileInput),
createInputMap(fileInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath("file")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool()))
.build());
}
@Test
public void testFileInputWithDirectoryContents(
@TestParameter InputsMode inputsMode, @TestParameter DirContents dirContents)
throws Exception {
Artifact fileInput = ActionsTestUtil.createArtifact(rootDir, "file");
fileInput.getPath().createDirectoryAndParents();
if (!dirContents.isEmpty()) {
writeFile(fileInput.getPath().getChild("file"), "abc");
}
SpawnBuilder spawn = defaultSpawnBuilder().withInputs(fileInput);
if (inputsMode.isTool()) {
spawn.withTools(fileInput);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(fileInput),
createInputMap(fileInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addAllInputs(
dirContents.isEmpty()
? ImmutableList.of()
: ImmutableList.of(
File.newBuilder()
.setPath("file/file")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool())
.build()))
.build());
}
@Test
public void testDirectoryInput(
@TestParameter InputsMode inputsMode, @TestParameter DirContents dirContents)
throws Exception {
Artifact dirInput = ActionsTestUtil.createArtifact(rootDir, "dir");
dirInput.getPath().createDirectoryAndParents();
if (!dirContents.isEmpty()) {
writeFile(dirInput.getPath().getChild("file"), "abc");
}
SpawnBuilder spawn = defaultSpawnBuilder().withInputs(dirInput);
if (inputsMode.equals(InputsMode.TOOLS)) {
spawn.withTools(dirInput);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(dirInput),
createInputMap(dirInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addAllInputs(
dirContents.isEmpty()
? ImmutableList.of()
: ImmutableList.of(
File.newBuilder()
.setPath("dir/file")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool())
.build()))
.build());
}
@Test
public void testTreeInput(
@TestParameter InputsMode inputsMode, @TestParameter DirContents dirContents)
throws Exception {
SpecialArtifact treeInput =
ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputDir, "tree");
treeInput.getPath().createDirectoryAndParents();
if (!dirContents.isEmpty()) {
writeFile(treeInput.getPath().getChild("child"), "abc");
}
SpawnBuilder spawn = defaultSpawnBuilder().withInputs(treeInput);
if (inputsMode.isTool()) {
spawn.withTools(treeInput);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(treeInput),
createInputMap(treeInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addAllInputs(
dirContents.isEmpty()
? ImmutableList.of()
: ImmutableList.of(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/tree/child")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool())
.build()))
.build());
}
@Test
public void testUnresolvedSymlinkInput(@TestParameter InputsMode inputsMode) throws Exception {
Artifact symlinkInput = ActionsTestUtil.createUnresolvedSymlinkArtifact(outputDir, "symlink");
symlinkInput.getPath().getParentDirectory().createDirectoryAndParents();
symlinkInput.getPath().createSymbolicLink(PathFragment.create("/some/path"));
SpawnBuilder spawn = defaultSpawnBuilder().withInputs(symlinkInput);
if (inputsMode.isTool()) {
spawn.withTools(symlinkInput);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(symlinkInput),
createInputMap(symlinkInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/symlink")
.setSymlinkTargetPath("/some/path")
.setIsTool(inputsMode.isTool()))
.build());
}
@Test
public void testRunfilesFileInput(@TestParameter InputsMode inputsMode) throws Exception {
Artifact runfilesInput = ActionsTestUtil.createArtifact(rootDir, "data.txt");
Artifact runfilesArtifact = ActionsTestUtil.createRunfilesArtifact(outputDir, "foo.runfiles");
writeFile(runfilesInput, "abc");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("foo.runfiles");
RunfilesTree runfilesTree = createRunfilesTree(runfilesRoot, runfilesInput);
SpawnBuilder spawnBuilder = defaultSpawnBuilder().withInput(runfilesArtifact);
if (inputsMode.isTool()) {
spawnBuilder.withTool(runfilesArtifact);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawnBuilder.build(),
createInputMetadataProvider(runfilesTree, runfilesArtifact, runfilesInput),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/"
+ WORKSPACE_NAME
+ "/data.txt")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool()))
.build());
}
@Test
public void testRunfilesNestedMiddleman() throws Exception {
Artifact runfilesMiddleman = ActionsTestUtil.createRunfilesArtifact(outputDir, "runfiles");
Artifact runfilesInput = ActionsTestUtil.createArtifact(rootDir, "data.txt");
writeFile(runfilesInput, "abc");
Artifact toolFile1 = ActionsTestUtil.createArtifact(rootDir, "tool1");
writeFile(toolFile1, "def");
Artifact toolFile2 = ActionsTestUtil.createArtifact(rootDir, "tool2");
writeFile(toolFile2, "ghi");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("foo.runfiles");
RunfilesTree runfilesTree = createRunfilesTree(runfilesRoot, runfilesInput);
NestedSet<ActionInput> tools =
NestedSetBuilder.<ActionInput>stableOrder()
.add(toolFile1)
.addTransitive(
NestedSetBuilder.<ActionInput>stableOrder()
.add(runfilesMiddleman)
.add(toolFile2)
.build())
.build();
Spawn spawn = defaultSpawnBuilder().withInputs(tools).withTools(tools).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree, runfilesMiddleman, runfilesInput, toolFile1, toolFile2),
createInputMap(runfilesTree, toolFile1, toolFile2),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/"
+ WORKSPACE_NAME
+ "/data.txt")
.setDigest(getDigest("abc"))
.setIsTool(true))
.addInputs(
File.newBuilder().setPath("tool1").setDigest(getDigest("def")).setIsTool(true))
.addInputs(
File.newBuilder().setPath("tool2").setDigest(getDigest("ghi")).setIsTool(true))
.build());
}
@Test
public void testRunfilesDirectoryInput(
@TestParameter DirContents dirContents, @TestParameter InputsMode inputsMode)
throws Exception {
Artifact runfilesArtifact = ActionsTestUtil.createRunfilesArtifact(outputDir, "runfiles");
Artifact runfilesInput = ActionsTestUtil.createArtifact(rootDir, "dir");
runfilesInput.getPath().createDirectoryAndParents();
if (!dirContents.isEmpty()) {
writeFile(runfilesInput.getPath().getChild("data.txt"), "abc");
}
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("foo.runfiles");
RunfilesTree runfilesTree = createRunfilesTree(runfilesRoot, runfilesInput);
SpawnBuilder spawnBuilder = defaultSpawnBuilder().withInput(runfilesArtifact);
if (inputsMode.isTool()) {
spawnBuilder.withTool(runfilesArtifact);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawnBuilder.build(),
createInputMetadataProvider(runfilesTree, runfilesArtifact, runfilesInput),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addAllInputs(
dirContents.isEmpty()
? ImmutableList.of()
: ImmutableList.of(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/"
+ WORKSPACE_NAME
+ "/dir/data.txt")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool())
.build()))
.build());
}
@Test
public void testRunfilesEmptyInput(@TestParameter InputsMode inputsMode) throws Exception {
Artifact runfilesArtifact = ActionsTestUtil.createRunfilesArtifact(outputDir, "runfiles");
Artifact runfilesInput = ActionsTestUtil.createArtifact(rootDir, "sub/dir/script.py");
writeFile(runfilesInput, "abc");
PackageIdentifier someRepoPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("pkg"));
Artifact externalSourceArtifact =
ActionsTestUtil.createArtifact(
externalSourceRoot,
someRepoPkg.getExecPath(siblingRepositoryLayout).getChild("lib.py").getPathString());
writeFile(externalSourceArtifact, "external_source");
PackageIdentifier someRepoOtherPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("other/pkg"));
Artifact externalGenArtifact =
ActionsTestUtil.createArtifact(
externalOutputDir,
someRepoOtherPkg
.getPackagePath(siblingRepositoryLayout)
.getChild("gen.py")
.getPathString());
writeFile(externalGenArtifact, "external_gen");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot, runfilesInput, externalGenArtifact, externalSourceArtifact);
SpawnBuilder spawnBuilder = defaultSpawnBuilder().withInput(runfilesArtifact);
if (inputsMode.isTool()) {
spawnBuilder.withTool(runfilesArtifact);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawnBuilder.build(),
createInputMetadataProvider(
runfilesTree,
runfilesArtifact,
runfilesInput,
externalGenArtifact,
externalSourceArtifact),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/foo.runfiles/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/"
+ WORKSPACE_NAME
+ "/sub/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/"
+ WORKSPACE_NAME
+ "/sub/dir/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/"
+ WORKSPACE_NAME
+ "/sub/dir/script.py")
.setDigest(getDigest("abc"))
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME + "-out/k8-fastbuild/bin/foo.runfiles/some_repo/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/some_repo/other/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/some_repo/other/pkg/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/some_repo/other/pkg/gen.py")
.setDigest(getDigest("external_gen"))
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/foo.runfiles/some_repo/pkg/__init__.py")
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME + "-out/k8-fastbuild/bin/foo.runfiles/some_repo/pkg/lib.py")
.setDigest(getDigest("external_source"))
.setIsTool(inputsMode.isTool()))
.build());
}
@Test
public void testRunfilesMixedRoots(@TestParameter boolean legacyExternalRunfiles)
throws Exception {
Artifact sourceArtifact = ActionsTestUtil.createArtifact(rootDir, "pkg/source.txt");
writeFile(sourceArtifact, "source");
Artifact genArtifact = ActionsTestUtil.createArtifact(outputDir, "other/pkg/gen.txt");
writeFile(genArtifact, "gen");
PackageIdentifier someRepoPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("pkg"));
Artifact externalSourceArtifact =
ActionsTestUtil.createArtifact(
externalSourceRoot,
someRepoPkg
.getExecPath(siblingRepositoryLayout)
.getChild("source.txt")
.getPathString());
writeFile(externalSourceArtifact, "external_source");
PackageIdentifier someRepoOtherPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("other/pkg"));
Artifact externalGenArtifact =
ActionsTestUtil.createArtifact(
externalOutputDir,
someRepoOtherPkg
.getPackagePath(siblingRepositoryLayout)
.getChild("gen.txt")
.getPathString());
writeFile(externalGenArtifact, "external_gen");
Artifact symlinkSourceTarget = ActionsTestUtil.createArtifact(rootDir, "pkg/target.txt");
writeFile(symlinkSourceTarget, "symlink_source");
Artifact symlinkGenTarget = ActionsTestUtil.createArtifact(outputDir, "pkg/target.txt");
writeFile(symlinkGenTarget, "symlink_gen");
Artifact rootSymlinkSourceTarget =
ActionsTestUtil.createArtifact(rootDir, "pkg/root_target.txt");
writeFile(rootSymlinkSourceTarget, "root_symlink_source");
Artifact rootSymlinkGenTarget =
ActionsTestUtil.createArtifact(outputDir, "pkg/root_target.txt");
writeFile(rootSymlinkGenTarget, "root_symlink_gen");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(
"some/symlink", symlinkSourceTarget,
"other/symlink", symlinkGenTarget),
ImmutableMap.of(
"root/symlink", rootSymlinkSourceTarget,
"root/other/symlink", rootSymlinkGenTarget),
legacyExternalRunfiles,
sourceArtifact,
genArtifact,
externalSourceArtifact,
externalGenArtifact);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree,
runfilesArtifact,
sourceArtifact,
genArtifact,
externalSourceArtifact,
externalGenArtifact,
symlinkSourceTarget,
symlinkGenTarget,
rootSymlinkSourceTarget,
rootSymlinkGenTarget),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
var builder = defaultSpawnExecBuilder();
if (legacyExternalRunfiles) {
builder
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/other/pkg/gen.txt")
.setDigest(getDigest("external_gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/pkg/source.txt")
.setDigest(getDigest("external_source")));
}
builder
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/other/pkg/gen.txt")
.setDigest(getDigest("gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/other/symlink")
.setDigest(getDigest("symlink_gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/source.txt")
.setDigest(getDigest("source")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/some/symlink")
.setDigest(getDigest("symlink_source")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME + "-out/k8-fastbuild/bin/tools/foo.runfiles/root/other/symlink")
.setDigest(getDigest("root_symlink_gen")))
.addInputs(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/tools/foo.runfiles/root/symlink")
.setDigest(getDigest("root_symlink_source")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/other/pkg/gen.txt")
.setDigest(getDigest("external_gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/pkg/source.txt")
.setDigest(getDigest("external_source")));
closeAndAssertLog(context, builder.build());
}
@Test
public void testRunfilesExternalOnly(
@TestParameter boolean legacyExternalRunfiles,
@TestParameter boolean symlinkUnderMain,
@TestParameter boolean rootSymlinkUnderMain)
throws Exception {
PackageIdentifier someRepoPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("pkg"));
Artifact externalSourceArtifact =
ActionsTestUtil.createArtifact(
externalSourceRoot,
someRepoPkg
.getExecPath(siblingRepositoryLayout)
.getChild("source.txt")
.getPathString());
writeFile(externalSourceArtifact, "external_source");
PackageIdentifier someRepoOtherPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("other/pkg"));
Artifact externalGenArtifact =
ActionsTestUtil.createArtifact(
externalOutputDir,
someRepoOtherPkg
.getPackagePath(siblingRepositoryLayout)
.getChild("gen.txt")
.getPathString());
writeFile(externalGenArtifact, "external_gen");
Artifact symlinkTarget = ActionsTestUtil.createArtifact(outputDir, "pkg/root_target.txt");
writeFile(symlinkTarget, "symlink_target");
Artifact rootSymlinkTarget = ActionsTestUtil.createArtifact(rootDir, "pkg/root_target.txt");
writeFile(rootSymlinkTarget, "root_symlink_target");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of((symlinkUnderMain ? "" : "../some_repo/") + "symlink", symlinkTarget),
ImmutableMap.of(
(rootSymlinkUnderMain ? WORKSPACE_NAME + "/" : "some_repo/") + "root_symlink",
rootSymlinkTarget),
legacyExternalRunfiles,
externalSourceArtifact,
externalGenArtifact);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree,
runfilesArtifact,
externalSourceArtifact,
externalGenArtifact,
symlinkTarget,
rootSymlinkTarget),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
ArrayList<File> files = new ArrayList<>();
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/%s/root_symlink"
.formatted(rootSymlinkUnderMain ? WORKSPACE_NAME : "some_repo"))
.setDigest(getDigest("root_symlink_target"))
.build());
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/%s/symlink"
.formatted(symlinkUnderMain ? WORKSPACE_NAME : "some_repo"))
.setDigest(getDigest("symlink_target"))
.build());
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/other/pkg/gen.txt")
.setDigest(getDigest("external_gen"))
.build());
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME + "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/pkg/source.txt")
.setDigest(getDigest("external_source"))
.build());
if (legacyExternalRunfiles) {
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/other/pkg/gen.txt")
.setDigest(getDigest("external_gen"))
.build());
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/pkg/source.txt")
.setDigest(getDigest("external_source"))
.build());
if (!symlinkUnderMain) {
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ ""
+ WORKSPACE_NAME
+ "/external/some_repo/symlink")
.setDigest(getDigest("symlink_target"))
.build());
}
} else if (!symlinkUnderMain && !rootSymlinkUnderMain) {
files.add(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/.runfile")
.build());
}
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addAllInputs(files.stream().sorted(comparing(File::getPath)).toList())
.build());
}
@Test
public void testRunfilesFilesCollide(@TestParameter boolean legacyExternalRunfiles)
throws Exception {
Artifact sourceArtifact = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(sourceArtifact, "source");
Artifact genArtifact = ActionsTestUtil.createArtifact(outputDir, "pkg/file.txt");
writeFile(genArtifact, "gen");
PackageIdentifier someRepoPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("pkg"));
Artifact externalSourceArtifact =
ActionsTestUtil.createArtifact(
externalSourceRoot,
someRepoPkg.getExecPath(siblingRepositoryLayout).getChild("file.txt").getPathString());
writeFile(externalSourceArtifact, "external_source");
Artifact externalGenArtifact =
ActionsTestUtil.createArtifact(
externalOutputDir,
someRepoPkg
.getPackagePath(siblingRepositoryLayout)
.getChild("file.txt")
.getPathString());
writeFile(externalGenArtifact, "external_gen");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(),
ImmutableMap.of(),
legacyExternalRunfiles,
sourceArtifact,
genArtifact,
externalSourceArtifact,
externalGenArtifact);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree,
runfilesArtifact,
sourceArtifact,
genArtifact,
externalSourceArtifact,
externalGenArtifact),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
var builder = defaultSpawnExecBuilder();
if (legacyExternalRunfiles) {
builder.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/pkg/file.txt")
.setDigest(getDigest("external_gen")));
}
builder
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setDigest(getDigest("gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/pkg/file.txt")
.setDigest(getDigest("external_gen")));
closeAndAssertLog(context, builder.build());
}
@Test
public void testRunfilesFilesAndSymlinksCollide(@TestParameter boolean legacyExternalRunfiles)
throws Exception {
Artifact sourceArtifact = ActionsTestUtil.createArtifact(rootDir, "pkg/source.txt");
writeFile(sourceArtifact, "source");
Artifact genArtifact = ActionsTestUtil.createArtifact(outputDir, "other/pkg/gen.txt");
writeFile(genArtifact, "gen");
PackageIdentifier someRepoPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("pkg"));
Artifact externalSourceArtifact =
ActionsTestUtil.createArtifact(
externalSourceRoot,
someRepoPkg
.getExecPath(siblingRepositoryLayout)
.getChild("source.txt")
.getPathString());
writeFile(externalSourceArtifact, "external_source");
PackageIdentifier someRepoOtherPkg =
PackageIdentifier.create(externalRepo, PathFragment.create("other/pkg"));
Artifact externalGenArtifact =
ActionsTestUtil.createArtifact(
externalOutputDir,
someRepoOtherPkg
.getPackagePath(siblingRepositoryLayout)
.getChild("gen.txt")
.getPathString());
writeFile(externalGenArtifact, "external_gen");
Artifact symlinkSourceArtifact = ActionsTestUtil.createArtifact(rootDir, "pkg/not_source.txt");
writeFile(symlinkSourceArtifact, "symlink_source");
Artifact symlinkGenArtifact =
ActionsTestUtil.createArtifact(outputDir, "other/pkg/not_gen.txt");
writeFile(symlinkGenArtifact, "symlink_gen");
Artifact symlinkExternalSourceArtifact =
ActionsTestUtil.createArtifact(externalSourceRoot, "external/some_repo/pkg/not_source.txt");
writeFile(symlinkExternalSourceArtifact, "symlink_external_source");
Artifact symlinkExternalGenArtifact =
ActionsTestUtil.createArtifact(outputDir, "external/some_repo/other/pkg/not_gen.txt");
writeFile(symlinkExternalGenArtifact, "symlink_external_gen");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(
// Symlinks are always relative to the workspace runfiles directory.
"pkg/source.txt", symlinkSourceArtifact,
"other/pkg/gen.txt", symlinkGenArtifact,
"../some_repo/pkg/source.txt", symlinkExternalSourceArtifact,
"../some_repo/other/pkg/gen.txt", symlinkExternalGenArtifact),
ImmutableMap.of(),
legacyExternalRunfiles,
sourceArtifact,
genArtifact,
externalSourceArtifact,
externalGenArtifact);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree,
runfilesArtifact,
sourceArtifact,
genArtifact,
externalSourceArtifact,
externalGenArtifact,
symlinkSourceArtifact,
symlinkGenArtifact,
symlinkExternalSourceArtifact,
symlinkExternalGenArtifact),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
var builder = defaultSpawnExecBuilder();
if (legacyExternalRunfiles) {
builder
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/other/pkg/gen.txt")
.setDigest(getDigest("external_gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/external/some_repo/pkg/source.txt")
.setDigest(getDigest("external_source")));
}
builder
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/other/pkg/gen.txt")
.setDigest(getDigest("gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/source.txt")
.setDigest(getDigest("source")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/other/pkg/gen.txt")
.setDigest(getDigest("external_gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/some_repo/pkg/source.txt")
.setDigest(getDigest("external_source")));
closeAndAssertLog(context, builder.build());
}
@Test
public void testRunfilesFileAndRootSymlinkCollide() throws Exception {
Artifact sourceArtifact = ActionsTestUtil.createArtifact(rootDir, "pkg/source.txt");
writeFile(sourceArtifact, "source");
Artifact symlinkSourceArtifact = ActionsTestUtil.createArtifact(rootDir, "pkg/not_source.txt");
writeFile(symlinkSourceArtifact, "symlink_source");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(),
ImmutableMap.of(WORKSPACE_NAME + "/pkg/source.txt", symlinkSourceArtifact),
/* legacyExternalRunfiles= */ false,
sourceArtifact);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree, runfilesArtifact, sourceArtifact, symlinkSourceArtifact),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/source.txt")
.setDigest(getDigest("symlink_source")))
.build());
}
@Test
public void testRunfilesCrossTypeCollision(@TestParameter boolean symlinkFirst) throws Exception {
Artifact file = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(file, "file");
Artifact symlink = ActionsTestUtil.createUnresolvedSymlinkArtifact(outputDir, "pkg/file.txt");
symlink.getPath().getParentDirectory().createDirectoryAndParents();
symlink.getPath().createSymbolicLink(PathFragment.create("/some/path/other_file.txt"));
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
var artifacts =
symlinkFirst ? ImmutableList.of(symlink, file) : ImmutableList.of(file, symlink);
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(),
ImmutableMap.of(),
/* legacyExternalRunfiles= */ false,
NestedSetBuilder.wrap(Order.STABLE_ORDER, artifacts));
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(runfilesTree, runfilesArtifact, file, symlink),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
symlinkFirst
? File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setDigest(getDigest("file"))
: File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setSymlinkTargetPath("/some/path/other_file.txt"))
.build());
}
@Test
public void testRunfilesPostOrderCollision(@TestParameter boolean nestBoth) throws Exception {
Artifact sourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(sourceFile, "source");
Artifact genFile = ActionsTestUtil.createArtifact(outputDir, "pkg/file.txt");
writeFile(genFile, "gen");
Artifact otherSourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/other_file.txt");
writeFile(otherSourceFile, "other_source");
Artifact otherGenFile = ActionsTestUtil.createArtifact(outputDir, "pkg/other_file.txt");
writeFile(otherGenFile, "other_gen");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
var artifactsBuilder =
NestedSetBuilder.<Artifact>compileOrder()
.addTransitive(
NestedSetBuilder.wrap(
Order.COMPILE_ORDER, ImmutableList.of(sourceFile, otherGenFile)));
var remainingArtifacts = ImmutableList.of(genFile, otherSourceFile);
if (nestBoth) {
artifactsBuilder.addTransitive(
NestedSetBuilder.wrap(Order.COMPILE_ORDER, remainingArtifacts));
} else {
artifactsBuilder.addAll(remainingArtifacts);
}
var artifacts = artifactsBuilder.build();
assertThat(artifacts.toList())
.containsExactly(sourceFile, otherGenFile, genFile, otherSourceFile)
.inOrder();
if (nestBoth) {
assertThat(artifacts.getNonLeaves()).hasSize(2);
}
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(),
ImmutableMap.of(),
/* legacyExternalRunfiles= */ false,
artifacts);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(
runfilesTree, runfilesArtifact, sourceFile, genFile, otherSourceFile, otherGenFile),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setDigest(getDigest("gen")))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/other_file.txt")
.setDigest(getDigest("other_source")))
.build());
}
@Test
public void testRunfilesArtifactPostOrderCollisionWithDuplicate() throws Exception {
Artifact sourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(sourceFile, "source");
Artifact genFile = ActionsTestUtil.createArtifact(outputDir, "pkg/file.txt");
writeFile(genFile, "gen");
Artifact runfilesMiddleman =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
var artifacts =
NestedSetBuilder.<Artifact>compileOrder()
.add(sourceFile)
.addTransitive(
NestedSetBuilder.wrap(Order.COMPILE_ORDER, ImmutableList.of(sourceFile, genFile)))
.build();
assertThat(artifacts.toList()).containsExactly(sourceFile, genFile).inOrder();
assertThat(artifacts.getLeaves()).hasSize(1);
assertThat(artifacts.getNonLeaves()).hasSize(1);
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
ImmutableMap.of(),
ImmutableMap.of(),
/* legacyExternalRunfiles= */ false,
artifacts);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesMiddleman).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(runfilesTree, runfilesMiddleman, sourceFile, genFile),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setDigest(getDigest("gen")))
.build());
}
@Test
public void testRunfilesSymlinkPostOrderCollisionWithSemanticDuplicate(
@TestParameter boolean rootSymlink) throws Exception {
Artifact sourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(sourceFile, "source");
Artifact genFile = ActionsTestUtil.createArtifact(outputDir, "pkg/file.txt");
writeFile(genFile, "gen");
Artifact runfilesMiddleman =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
Runfiles.Builder runfiles =
new Runfiles.Builder(WORKSPACE_NAME, /* legacyExternalRunfiles= */ false);
if (rootSymlink) {
Runfiles transitiveRunfiles =
new Runfiles.Builder(WORKSPACE_NAME, /* legacyExternalRunfiles= */ false)
.addRootSymlink(PathFragment.create(WORKSPACE_NAME + "/pkg/file.txt"), sourceFile)
.addRootSymlink(PathFragment.create(WORKSPACE_NAME + "/pkg/file.txt"), genFile)
.build();
runfiles.addRootSymlink(PathFragment.create(WORKSPACE_NAME + "/pkg/file.txt"), sourceFile);
runfiles.addRootSymlinks(transitiveRunfiles.getRootSymlinks());
} else {
Runfiles transitiveRunfiles =
new Runfiles.Builder(WORKSPACE_NAME, /* legacyExternalRunfiles= */ false)
.addSymlink(PathFragment.create("pkg/file.txt"), sourceFile)
.addSymlink(PathFragment.create("pkg/file.txt"), genFile)
.build();
runfiles.addSymlink(PathFragment.create("pkg/file.txt"), sourceFile);
runfiles.addSymlinks(transitiveRunfiles.getSymlinks());
}
RunfilesTree runfilesTree =
new RunfilesSupport.RunfilesTreeImpl(runfilesRoot, runfiles.build());
Spawn spawn = defaultSpawnBuilder().withInput(runfilesMiddleman).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(runfilesTree, runfilesMiddleman, sourceFile, genFile),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setDigest(getDigest("source")))
.build());
}
@Test
public void testRunfilesSymlinkPostOrderCollisionWithEqualDuplicate(
@TestParameter boolean rootSymlink) throws Exception {
Artifact sourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(sourceFile, "source");
Artifact genFile = ActionsTestUtil.createArtifact(outputDir, "pkg/file.txt");
writeFile(genFile, "gen");
Artifact runfilesMiddleman =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
Runfiles.Builder runfiles =
new Runfiles.Builder(WORKSPACE_NAME, /* legacyExternalRunfiles= */ false);
// Arrange for (reference) equal SymlinkEntry instances to appear twice in the runfiles, both
// first and last in compile order.
if (rootSymlink) {
Runfiles transitiveRunfiles =
new Runfiles.Builder(WORKSPACE_NAME, /* legacyExternalRunfiles= */ false)
.addRootSymlink(PathFragment.create(WORKSPACE_NAME + "/pkg/file.txt"), sourceFile)
.addRootSymlink(PathFragment.create(WORKSPACE_NAME + "/pkg/file.txt"), genFile)
.build();
runfiles.addRootSymlinks(
NestedSetBuilder.wrap(
Order.STABLE_ORDER, transitiveRunfiles.getRootSymlinks().toList().subList(0, 1)));
runfiles.addRootSymlinks(transitiveRunfiles.getRootSymlinks());
} else {
Runfiles transitiveRunfiles =
new Runfiles.Builder(WORKSPACE_NAME, /* legacyExternalRunfiles= */ false)
.addSymlink(PathFragment.create("pkg/file.txt"), sourceFile)
.addSymlink(PathFragment.create("pkg/file.txt"), genFile)
.build();
runfiles.addSymlinks(
NestedSetBuilder.wrap(
Order.STABLE_ORDER, transitiveRunfiles.getSymlinks().toList().subList(0, 1)));
runfiles.addSymlinks(transitiveRunfiles.getSymlinks());
}
RunfilesTree runfilesTree =
new RunfilesSupport.RunfilesTreeImpl(runfilesRoot, runfiles.build());
Spawn spawn = defaultSpawnBuilder().withInput(runfilesMiddleman).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(runfilesTree, runfilesMiddleman, sourceFile, genFile),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
// The compact log can't distinguish between semantically equal and reference equal SymlinkEntry
// instances and thus the reconstructor can't deduplicate them as NestedSet would. This is a
// pathological case that can be recreated in Starlark, but it would cause a conflict error
// unless using --nobuild_runfile_manifests.
String expectedContent = this instanceof CompactSpawnLogContextTest ? "source" : "gen";
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/file.txt")
.setDigest(getDigest(expectedContent)))
.build());
}
@Test
public void testRunfilesSymlinkTargets(
@TestParameter boolean rootSymlinks, @TestParameter InputsMode inputsMode) throws Exception {
Artifact sourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
writeFile(sourceFile, "source");
Artifact sourceDir = ActionsTestUtil.createArtifact(rootDir, "pkg/source_dir");
sourceDir.getPath().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(
sourceDir.getPath().getRelative("some_file"), "source_dir_file");
Artifact genDir =
ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputDir, "pkg/gen_dir");
genDir.getPath().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(
genDir.getPath().getRelative("other_file"), "gen_dir_file");
Artifact symlink = ActionsTestUtil.createUnresolvedSymlinkArtifact(outputDir, "pkg/symlink");
symlink.getPath().getParentDirectory().createDirectoryAndParents();
symlink.getPath().createSymbolicLink(PathFragment.create("/some/path"));
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
rootSymlinks
? ImmutableMap.of()
: ImmutableMap.of(
"file", sourceFile,
"source_dir", sourceDir,
"gen_dir", genDir,
"symlink", symlink),
rootSymlinks
? ImmutableMap.of(
WORKSPACE_NAME + "/file", sourceFile,
WORKSPACE_NAME + "/source_dir", sourceDir,
WORKSPACE_NAME + "/gen_dir", genDir,
WORKSPACE_NAME + "/symlink", symlink)
: ImmutableMap.of(),
/* legacyExternalRunfiles= */ false);
var spawnBuilder = defaultSpawnBuilder().withInput(runfilesArtifact);
if (inputsMode.isTool()) {
spawnBuilder.withTool(runfilesArtifact);
}
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawnBuilder.build(),
createInputMetadataProvider(
runfilesTree, runfilesArtifact, sourceFile, sourceDir, genDir, symlink),
createInputMap(runfilesTree),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/file")
.setDigest(getDigest("source"))
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/gen_dir/other_file")
.setDigest(getDigest("gen_dir_file"))
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/source_dir/some_file")
.setDigest(getDigest("source_dir_file"))
.setIsTool(inputsMode.isTool()))
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/symlink")
.setSymlinkTargetPath("/some/path")
.setIsTool(inputsMode.isTool()))
.build());
}
@Test
public void testRunfileSymlinkFileWithDirectoryContents(
@TestParameter boolean rootSymlink, @TestParameter OutputsMode outputsMode) throws Exception {
Artifact sourceFile = ActionsTestUtil.createArtifact(rootDir, "pkg/file.txt");
sourceFile.getPath().createDirectoryAndParents();
writeFile(sourceFile.getPath().getChild("file"), "abc");
Artifact runfilesArtifact =
ActionsTestUtil.createRunfilesArtifact(outputDir, "tools/foo.runfiles");
PathFragment runfilesRoot = outputDir.getExecPath().getRelative("tools/foo.runfiles");
RunfilesTree runfilesTree =
createRunfilesTree(
runfilesRoot,
rootSymlink ? ImmutableMap.of() : ImmutableMap.of("pkg/symlink", sourceFile),
rootSymlink
? ImmutableMap.of(WORKSPACE_NAME + "/pkg/symlink", sourceFile)
: ImmutableMap.of(),
/* legacyExternalRunfiles= */ false);
Spawn spawn = defaultSpawnBuilder().withInput(runfilesArtifact).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(runfilesTree, runfilesArtifact, sourceFile),
createInputMap(runfilesTree),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(
File.newBuilder()
.setPath(
PRODUCT_NAME
+ "-out/k8-fastbuild/bin/tools/foo.runfiles/"
+ WORKSPACE_NAME
+ "/pkg/symlink/file")
.setDigest(getDigest("abc")))
.build());
}
@Test
public void testFilesetInput(@TestParameter DirContents dirContents) throws Exception {
Artifact filesetInput =
SpecialArtifact.create(
outputDir,
outputDir.getExecPath().getRelative("dir"),
ActionsTestUtil.NULL_ARTIFACT_OWNER,
SpecialArtifactType.FILESET);
filesetInput.getPath().createDirectoryAndParents();
if (!dirContents.isEmpty()) {
writeFile(fs.getPath("/file.txt"), "abc");
filesetInput
.getPath()
.getChild("file.txt")
.createSymbolicLink(PathFragment.create("/file.txt"));
}
Spawn spawn =
defaultSpawnBuilder()
.withInput(filesetInput)
// The implementation only relies on the map keys, so the value can be empty.
.withFilesetMapping(filesetInput, FilesetOutputTree.EMPTY)
.build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(filesetInput),
createInputMap(filesetInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addAllInputs(
dirContents.isEmpty()
? ImmutableList.of()
: ImmutableList.of(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/dir/file.txt")
.setDigest(getDigest("abc"))
.build()))
.build());
}
@Test
public void testParamFileInput() throws Exception {
ParamFileActionInput paramFileInput =
new ParamFileActionInput(
PathFragment.create("foo.params"),
ImmutableList.of("a", "b", "c"),
ParameterFileType.UNQUOTED,
UTF_8);
// Do not materialize the file on disk, which would be the case when running remotely.
SpawnBuilder spawn = defaultSpawnBuilder().withInputs(paramFileInput);
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
// ParamFileActionInputs appear in the input map but not in the metadata provider.
createInputMetadataProvider(),
createInputMap(paramFileInput),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addInputs(File.newBuilder().setPath("foo.params").setDigest(getDigest("a\nb\nc\n")))
.build());
}
@Test
public void testFileOutput(
@TestParameter OutputsMode outputsMode, @TestParameter OutputIndirection indirection)
throws Exception {
Artifact fileOutput = ActionsTestUtil.createArtifact(outputDir, "file");
Path actualPath =
indirection.viaSymlink()
? outputDir.getRoot().asPath().getChild("actual")
: fileOutput.getPath();
if (indirection.viaSymlink()) {
fileOutput.getPath().getParentDirectory().createDirectoryAndParents();
fileOutput.getPath().createSymbolicLink(actualPath);
}
writeFile(actualPath, "abc");
Spawn spawn = defaultSpawnBuilder().withOutputs(fileOutput).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/file")
.addActualOutputs(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/file")
.setDigest(getDigest("abc")))
.build());
}
@Test
public void testFileOutputWithInvalidType(@TestParameter OutputsMode outputsMode)
throws Exception {
Artifact fileOutput = ActionsTestUtil.createArtifact(outputDir, "file");
fileOutput.getPath().createDirectoryAndParents();
writeFile(fileOutput.getPath().getChild("file"), "abc");
SpawnBuilder spawn = defaultSpawnBuilder().withOutputs(fileOutput);
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/file")
.build());
}
@Test
public void testTreeOutput(
@TestParameter OutputsMode outputsMode,
@TestParameter DirContents dirContents,
@TestParameter OutputIndirection indirection)
throws Exception {
SpecialArtifact treeOutput =
ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputDir, "tree");
Path actualPath =
indirection.viaSymlink()
? outputDir.getRoot().asPath().getChild("actual")
: treeOutput.getPath();
if (indirection.viaSymlink()) {
treeOutput.getPath().getParentDirectory().createDirectoryAndParents();
treeOutput.getPath().createSymbolicLink(actualPath);
}
actualPath.createDirectoryAndParents();
if (!dirContents.isEmpty()) {
Path firstChildPath = actualPath.getRelative("dir1/file1");
Path secondChildPath = actualPath.getRelative("dir2/file2");
firstChildPath.getParentDirectory().createDirectoryAndParents();
secondChildPath.getParentDirectory().createDirectoryAndParents();
writeFile(firstChildPath, "abc");
writeFile(secondChildPath, "def");
Path emptySubdirPath = actualPath.getRelative("dir3");
emptySubdirPath.createDirectoryAndParents();
}
Spawn spawn = defaultSpawnBuilder().withOutputs(treeOutput).build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/tree")
.addAllActualOutputs(
dirContents.isEmpty()
? ImmutableList.of()
: ImmutableList.of(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/tree/dir1/file1")
.setDigest(getDigest("abc"))
.build(),
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/tree/dir2/file2")
.setDigest(getDigest("def"))
.build()))
.build());
}
@Test
public void testTreeOutputWithInvalidType(@TestParameter OutputsMode outputsMode)
throws Exception {
Artifact treeOutput = ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputDir, "tree");
writeFile(treeOutput, "abc");
SpawnBuilder spawn = defaultSpawnBuilder().withOutputs(treeOutput);
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/tree")
.build());
}
@Test
public void testUnresolvedSymlinkOutput(@TestParameter OutputsMode outputsMode) throws Exception {
Artifact symlinkOutput = ActionsTestUtil.createUnresolvedSymlinkArtifact(outputDir, "symlink");
symlinkOutput.getPath().getParentDirectory().createDirectoryAndParents();
symlinkOutput.getPath().createSymbolicLink(PathFragment.create("/some/path"));
SpawnBuilder spawn = defaultSpawnBuilder().withOutputs(symlinkOutput);
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/symlink")
.addActualOutputs(
File.newBuilder()
.setPath(PRODUCT_NAME + "-out/k8-fastbuild/bin/symlink")
.setSymlinkTargetPath("/some/path"))
.build());
}
@Test
public void testUnresolvedSymlinkOutputWithInvalidType(@TestParameter OutputsMode outputsMode)
throws Exception {
Artifact symlinkOutput = ActionsTestUtil.createUnresolvedSymlinkArtifact(outputDir, "symlink");
writeFile(symlinkOutput, "abc");
SpawnBuilder spawn = defaultSpawnBuilder().withOutputs(symlinkOutput);
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/symlink")
.build());
}
@Test
public void testMissingOutput(@TestParameter OutputsMode outputsMode) throws Exception {
Artifact missingOutput = ActionsTestUtil.createArtifact(outputDir, "missing");
SpawnBuilder spawn = defaultSpawnBuilder().withOutputs(missingOutput);
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn.build(),
createInputMetadataProvider(),
createInputMap(),
outputsMode.getActionFileSystem(fs),
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addListedOutputs(PRODUCT_NAME + "-out/k8-fastbuild/bin/missing")
.build());
}
@Test
public void testEnvironment() throws Exception {
Spawn spawn =
defaultSpawnBuilder().withEnvironment("SPAM", "eggs").withEnvironment("FOO", "bar").build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.addEnvironmentVariables(
EnvironmentVariable.newBuilder().setName("FOO").setValue("bar"))
.addEnvironmentVariables(
EnvironmentVariable.newBuilder().setName("SPAM").setValue("eggs"))
.build());
}
@Test
public void testDefaultPlatformProperties() throws Exception {
SpawnLogContext context = createSpawnLogContext(ImmutableMap.of("a", "1", "b", "2"));
context.logSpawn(
defaultSpawn(),
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.setPlatform(
Platform.newBuilder()
.addProperties(Platform.Property.newBuilder().setName("a").setValue("1"))
.addProperties(Platform.Property.newBuilder().setName("b").setValue("2"))
.build())
.build());
}
@Test
public void testSpawnPlatformProperties() throws Exception {
Spawn spawn =
defaultSpawnBuilder().withExecProperties(ImmutableMap.of("a", "3", "c", "4")).build();
SpawnLogContext context = createSpawnLogContext(ImmutableMap.of("a", "1", "b", "2"));
context.logSpawn(
spawn,
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
defaultSpawnResult());
// The spawn properties should override the default properties.
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.setPlatform(
Platform.newBuilder()
.addProperties(Platform.Property.newBuilder().setName("a").setValue("3"))
.addProperties(Platform.Property.newBuilder().setName("b").setValue("2"))
.addProperties(Platform.Property.newBuilder().setName("c").setValue("4"))
.build())
.build());
}
@Test
public void testExecutionInfo(
@TestParameter({"no-remote", "no-cache", "no-remote-cache"}) String requirement)
throws Exception {
Spawn spawn = defaultSpawnBuilder().withExecutionInfo(requirement, "").build();
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
spawn,
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.setRemotable(!requirement.equals("no-remote"))
.setCacheable(!requirement.equals("no-cache"))
.setRemoteCacheable(
!requirement.equals("no-cache")
&& !requirement.equals("no-remote")
&& !requirement.equals("no-remote-cache"))
.build());
}
@Test
public void testCacheHit() throws Exception {
SpawnLogContext context = createSpawnLogContext();
SpawnResult result = defaultSpawnResultBuilder().setCacheHit(true).build();
context.logSpawn(
defaultSpawn(),
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
result);
closeAndAssertLog(context, defaultSpawnExecBuilder().setCacheHit(true).build());
}
@Test
public void testDigest() throws Exception {
SpawnLogContext context = createSpawnLogContext();
Digest digest = getDigest("something");
SpawnResult result = defaultSpawnResultBuilder().setDigest(digest).build();
context.logSpawn(
defaultSpawn(),
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
result);
closeAndAssertLog(context, defaultSpawnExecBuilder().setDigest(digest).build());
}
@Test
public void testTimeout() throws Exception {
SpawnLogContext context = createSpawnLogContext();
context.logSpawn(
defaultSpawn(),
createInputMetadataProvider(),
createInputMap(),
fs,
/* timeout= */ Duration.ofSeconds(42),
defaultSpawnResult());
closeAndAssertLog(
context,
defaultSpawnExecBuilder().setTimeoutMillis(Duration.ofSeconds(42).toMillis()).build());
}
@Test
public void testSpawnMetrics() throws Exception {
SpawnMetrics metrics = SpawnMetrics.Builder.forLocalExec().setTotalTimeInMs(1).build();
SpawnLogContext context = createSpawnLogContext();
Instant now = Instant.now();
context.logSpawn(
defaultSpawn(),
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
defaultSpawnResultBuilder().setSpawnMetrics(metrics).setStartTime(now).build());
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.setMetrics(
Protos.SpawnMetrics.newBuilder()
.setTotalTime(millisToProto(1))
.setStartTime(Timestamps.fromDate(Date.from(now))))
.build());
}
@Test
public void testStatus() throws Exception {
SpawnLogContext context = createSpawnLogContext();
// SpawnResult requires a non-zero exit code and non-null failure details when the status isn't
// successful.
SpawnResult result =
defaultSpawnResultBuilder()
.setStatus(Status.NON_ZERO_EXIT)
.setExitCode(37)
.setFailureDetail(
FailureDetail.newBuilder()
.setMessage("oops")
.setCrash(Crash.getDefaultInstance())
.build())
.build();
context.logSpawn(
defaultSpawn(),
createInputMetadataProvider(),
createInputMap(),
fs,
defaultTimeout(),
result);
closeAndAssertLog(
context,
defaultSpawnExecBuilder()
.setExitCode(37)
.setStatus(Status.NON_ZERO_EXIT.toString())
.build());
}
protected static Duration defaultTimeout() {
return Duration.ZERO;
}
protected static SpawnBuilder defaultSpawnBuilder() {
return new SpawnBuilder("cmd", "--opt");
}
protected static Spawn defaultSpawn() {
return defaultSpawnBuilder().build();
}
protected static SpawnResult.Builder defaultSpawnResultBuilder() {
return new SpawnResult.Builder().setRunnerName("runner").setStatus(Status.SUCCESS);
}
protected static SpawnResult defaultSpawnResult() {
return defaultSpawnResultBuilder().build();
}
protected static SpawnExec.Builder defaultSpawnExecBuilder() {
return SpawnExec.newBuilder()
.addCommandArgs("cmd")
.addCommandArgs("--opt")
.setRunner("runner")
.setRemotable(true)
.setCacheable(true)
.setRemoteCacheable(true)
.setMnemonic("Mnemonic")
.setTargetLabel("//dummy:label")
.setMetrics(Protos.SpawnMetrics.getDefaultInstance());
}
protected static RunfilesTree createRunfilesTree(PathFragment root, Artifact... artifacts) {
return createRunfilesTree(
root, ImmutableMap.of(), ImmutableMap.of(), /* legacyExternalRunfiles= */ false, artifacts);
}
protected static RunfilesTree createRunfilesTree(
PathFragment root,
Map<String, Artifact> symlinks,
Map<String, Artifact> rootSymlinks,
boolean legacyExternalRunfiles,
NestedSet<Artifact> artifacts) {
Runfiles.Builder runfiles = new Runfiles.Builder(WORKSPACE_NAME, legacyExternalRunfiles);
runfiles.addTransitiveArtifacts(artifacts);
for (Map.Entry<String, Artifact> entry : symlinks.entrySet()) {
runfiles.addSymlink(PathFragment.create(entry.getKey()), entry.getValue());
}
for (Map.Entry<String, Artifact> entry : rootSymlinks.entrySet()) {
runfiles.addRootSymlink(PathFragment.create(entry.getKey()), entry.getValue());
}
runfiles.setEmptyFilesSupplier(BazelPyBuiltins.GET_INIT_PY_FILES);
return new RunfilesSupport.RunfilesTreeImpl(root, runfiles.build());
}
protected static RunfilesTree createRunfilesTree(
PathFragment root,
Map<String, Artifact> symlinks,
Map<String, Artifact> rootSymlinks,
boolean legacyExternalRunfiles,
Artifact... artifacts) {
return createRunfilesTree(
root,
symlinks,
rootSymlinks,
legacyExternalRunfiles,
NestedSetBuilder.wrap(Order.COMPILE_ORDER, Arrays.asList(artifacts)));
}
protected static InputMetadataProvider createInputMetadataProvider(Artifact... artifacts)
throws Exception {
return createInputMetadataProvider(null, artifacts);
}
protected static InputMetadataProvider createInputMetadataProvider(
RunfilesTree runfilesTree, Artifact... artifacts) throws Exception {
FakeActionInputFileCache builder = new FakeActionInputFileCache();
for (Artifact artifact : artifacts) {
if (artifact.isTreeArtifact()) {
// Emulate ActionInputMap: add both tree and children.
TreeArtifactValue treeMetadata = createTreeArtifactValue(artifact);
builder.put(artifact, treeMetadata.getMetadata());
for (Map.Entry<TreeFileArtifact, FileArtifactValue> entry :
treeMetadata.getChildValues().entrySet()) {
builder.put(entry.getKey(), entry.getValue());
}
} else if (artifact.isSymlink()) {
builder.put(artifact, FileArtifactValue.createForUnresolvedSymlink(artifact));
} else if (artifact.isMiddlemanArtifact()) {
builder.putRunfilesTree(artifact, runfilesTree);
} else {
builder.put(artifact, FileArtifactValue.createForTesting(artifact));
}
}
return builder;
}
protected static SortedMap<PathFragment, ActionInput> createInputMap(ActionInput... actionInputs)
throws Exception {
return createInputMap(null, actionInputs);
}
protected static SortedMap<PathFragment, ActionInput> createInputMap(
RunfilesTree runfilesTree, ActionInput... actionInputs) throws Exception {
TreeMap<PathFragment, ActionInput> builder = new TreeMap<>();
if (runfilesTree != null) {
new SpawnInputExpander(/* execRoot= */ null)
.addSingleRunfilesTreeToInputs(
runfilesTree,
builder,
treeArtifact -> {
try {
return createTreeArtifactValue(treeArtifact).getChildren();
} catch (Exception e) {
throw new ArtifactExpander.MissingExpansionException(e.getMessage());
}
},
PathMapper.NOOP,
PathFragment.EMPTY_FRAGMENT);
}
for (ActionInput actionInput : actionInputs) {
if (actionInput instanceof Artifact artifact && artifact.isTreeArtifact()) {
// Emulate SpawnInputExpander: expand to children, preserve if empty.
TreeArtifactValue treeMetadata = createTreeArtifactValue(artifact);
if (treeMetadata.getChildren().isEmpty()) {
builder.put(artifact.getExecPath(), artifact);
} else {
for (TreeFileArtifact child : treeMetadata.getChildren()) {
builder.put(child.getExecPath(), child);
}
}
} else {
builder.put(actionInput.getExecPath(), actionInput);
}
}
return ImmutableSortedMap.copyOf(builder);
}
protected static TreeArtifactValue createTreeArtifactValue(Artifact tree) throws Exception {
checkState(tree.isTreeArtifact());
TreeArtifactValue.Builder builder = TreeArtifactValue.newBuilder((SpecialArtifact) tree);
TreeArtifactValue.visitTree(
tree.getPath(),
(parentRelativePath, type, traversedSymlink) -> {
if (type.equals(Dirent.Type.DIRECTORY)) {
return;
}
TreeFileArtifact child =
TreeFileArtifact.createTreeOutput((SpecialArtifact) tree, parentRelativePath);
builder.putChild(child, FileArtifactValue.createForTesting(child));
});
return builder.build();
}
protected SpawnLogContext createSpawnLogContext() throws IOException, InterruptedException {
return createSpawnLogContext(ImmutableSortedMap.of());
}
protected abstract SpawnLogContext createSpawnLogContext(
ImmutableMap<String, String> platformProperties) throws IOException, InterruptedException;
protected Digest getDigest(String content) {
return Digest.newBuilder()
.setHash(digestHashFunction.getHashFunction().hashString(content, UTF_8).toString())
.setSizeBytes(Utf8.encodedLength(content))
.setHashFunctionName(digestHashFunction.toString())
.build();
}
protected static void writeFile(Artifact artifact, String contents) throws IOException {
writeFile(artifact.getPath(), contents);
}
protected static void writeFile(Path path, String contents) throws IOException {
path.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContent(path, UTF_8, contents);
}
protected abstract void closeAndAssertLog(SpawnLogContext context, SpawnExec... expected)
throws IOException, InterruptedException;
}