| // 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.devtools.build.lib.exec.SpawnLogContext.millisToProto; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static org.mockito.Mockito.verify; |
| |
| import com.google.common.base.Utf8; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSortedMap; |
| import com.google.devtools.build.lib.actions.ActionInput; |
| import com.google.devtools.build.lib.actions.ActionInputHelper; |
| 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.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.FileArtifactValue; |
| import com.google.devtools.build.lib.actions.InputMetadataProvider; |
| 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.StaticInputMetadataProvider; |
| import com.google.devtools.build.lib.actions.cache.VirtualActionInput; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| 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.SpawnBuilder; |
| import com.google.devtools.build.lib.remote.options.RemoteOptions; |
| 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.util.io.MessageOutputStream; |
| 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.SyscallCache; |
| import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; |
| import com.google.devtools.common.options.Options; |
| import com.google.testing.junit.testparameterinjector.TestParameter; |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; |
| import java.io.IOException; |
| import java.time.Duration; |
| import java.util.Map; |
| import java.util.SortedMap; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnit; |
| import org.mockito.junit.MockitoRule; |
| |
| /** Tests for {@link SpawnLogContext}. */ |
| @RunWith(TestParameterInjector.class) |
| public final class SpawnLogContextTest { |
| @Rule public final MockitoRule mockito = MockitoJUnit.rule(); |
| @Mock private MessageOutputStream<SpawnExec> outputStream; |
| |
| private final DigestHashFunction digestHashFunction = DigestHashFunction.SHA256; |
| private final FileSystem fs = new InMemoryFileSystem(digestHashFunction); |
| private final Path execRoot = fs.getPath("/execroot"); |
| private final ArtifactRoot rootDir = ArtifactRoot.asSourceRoot(Root.fromPath(execRoot)); |
| private final ArtifactRoot outputDir = |
| ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "out"); |
| |
| // 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). |
| private 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. */ |
| enum InputsMode { |
| TOOLS, |
| NON_TOOLS; |
| |
| boolean isTool() { |
| return this == TOOLS; |
| } |
| } |
| |
| /** Test parameter determining whether to emulate building with or without the bytes. */ |
| 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 |
| 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 spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn.build(), |
| createInputMetadataProvider(fileInput), |
| createInputMap(fileInput), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addInputs( |
| File.newBuilder() |
| .setPath("file") |
| .setDigest(getDigest("abc")) |
| .setIsTool(inputsMode.isTool())) |
| .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 spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn.build(), |
| createInputMetadataProvider(dirInput), |
| createInputMap(dirInput), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| // TODO(tjgq): Propagate tool bit to files inside source directories. |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addAllInputs( |
| dirContents.isEmpty() |
| ? ImmutableList.of() |
| : ImmutableList.of( |
| File.newBuilder() |
| .setPath("dir/file") |
| .setDigest(getDigest("abc")) |
| .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 spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn.build(), |
| createInputMetadataProvider(treeInput), |
| createInputMap(treeInput), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addAllInputs( |
| dirContents.isEmpty() |
| ? ImmutableList.of() |
| : ImmutableList.of( |
| File.newBuilder() |
| .setPath("out/tree/child") |
| .setDigest(getDigest("abc")) |
| .setIsTool(inputsMode.isTool()) |
| .build())) |
| .build()); |
| } |
| |
| @Test |
| public void testRunfilesInput() throws Exception { |
| Artifact runfilesInput = ActionsTestUtil.createArtifact(rootDir, "data.txt"); |
| |
| writeFile(runfilesInput, "abc"); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| // In reality, the spawn would have a RunfilesSupplier and a runfiles middleman input. |
| defaultSpawn(), |
| createInputMetadataProvider(runfilesInput), |
| /* inputMap= */ ImmutableSortedMap.of( |
| outputDir.getExecPath().getRelative("foo.runfiles/data.txt"), runfilesInput), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| // TODO(tjgq): The path should be foo.runfiles/data.txt. |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addInputs(File.newBuilder().setPath("data.txt").setDigest(getDigest("abc"))) |
| .build()); |
| } |
| |
| @Test |
| public void testAbsolutePathInput() throws Exception { |
| // Only observed to occur for source files inside a fileset. |
| Path absolutePath = fs.getPath("/some/file.txt"); |
| ActionInput absolutePathInput = ActionInputHelper.fromPath(absolutePath.asFragment()); |
| |
| writeFile(absolutePath, "abc"); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| new StaticInputMetadataProvider( |
| ImmutableMap.of(absolutePathInput, FileArtifactValue.createForTesting(absolutePath))), |
| /* inputMap= */ ImmutableSortedMap.of(absolutePath.asFragment(), absolutePathInput), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addInputs(File.newBuilder().setPath("/some/file.txt").setDigest(getDigest("abc"))) |
| .build()); |
| } |
| |
| @Test |
| public void testEmptyInput() throws Exception { |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| /* inputMap= */ ImmutableSortedMap.of( |
| outputDir.getExecPath().getRelative("__init__.py"), VirtualActionInput.EMPTY_MARKER), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| // TODO(tjgq): It would make more sense to report an empty file. |
| verify(outputStream).write(defaultSpawnExec()); |
| } |
| |
| @Test |
| public void testFileOutput(@TestParameter OutputsMode outputsMode) throws Exception { |
| Artifact fileOutput = ActionsTestUtil.createArtifact(outputDir, "file"); |
| |
| writeFile(fileOutput, "abc"); |
| |
| Spawn spawn = defaultSpawnBuilder().withOutputs(fileOutput).build(); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn, |
| createInputMetadataProvider(), |
| createInputMap(), |
| outputsMode.getActionFileSystem(fs), |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addListedOutputs("out/file") |
| .addActualOutputs(File.newBuilder().setPath("out/file").setDigest(getDigest("abc"))) |
| .build()); |
| } |
| |
| @Test |
| public void testDirectoryOutput( |
| @TestParameter OutputsMode outputsMode, @TestParameter DirContents dirContents) |
| throws Exception { |
| Artifact dirOutput = ActionsTestUtil.createArtifact(outputDir, "dir"); |
| |
| dirOutput.getPath().createDirectoryAndParents(); |
| if (!dirContents.isEmpty()) { |
| writeFile(dirOutput.getPath().getChild("file"), "abc"); |
| } |
| |
| SpawnBuilder spawn = defaultSpawnBuilder().withOutputs(dirOutput); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn.build(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| outputsMode.getActionFileSystem(fs), |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addListedOutputs("out/dir") |
| .addAllActualOutputs( |
| dirContents.isEmpty() |
| ? ImmutableList.of() |
| : ImmutableList.of( |
| File.newBuilder() |
| .setPath("out/dir/file") |
| .setDigest(getDigest("abc")) |
| .build())) |
| .build()); |
| } |
| |
| @Test |
| public void testTreeOutput( |
| @TestParameter OutputsMode outputsMode, @TestParameter DirContents dirContents) |
| throws Exception { |
| SpecialArtifact treeOutput = |
| ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputDir, "tree"); |
| |
| treeOutput.getPath().createDirectoryAndParents(); |
| if (!dirContents.isEmpty()) { |
| writeFile(treeOutput.getPath().getChild("child"), "abc"); |
| } |
| |
| Spawn spawn = defaultSpawnBuilder().withOutputs(treeOutput).build(); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn, |
| createInputMetadataProvider(), |
| createInputMap(), |
| outputsMode.getActionFileSystem(fs), |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addListedOutputs("out/tree") |
| .addAllActualOutputs( |
| dirContents.isEmpty() |
| ? ImmutableList.of() |
| : ImmutableList.of( |
| File.newBuilder() |
| .setPath("out/tree/child") |
| .setDigest(getDigest("abc")) |
| .build())) |
| .build()); |
| } |
| |
| @Test |
| public void testEnvironment() throws Exception { |
| Spawn spawn = |
| defaultSpawnBuilder().withEnvironment("SPAM", "eggs").withEnvironment("FOO", "bar").build(); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn, |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .addEnvironmentVariables( |
| EnvironmentVariable.newBuilder().setName("FOO").setValue("bar")) |
| .addEnvironmentVariables( |
| EnvironmentVariable.newBuilder().setName("SPAM").setValue("eggs")) |
| .build()); |
| } |
| |
| @Test |
| public void testDefaultPlatformProperties() throws Exception { |
| SpawnLogContext spawnLogContext = createSpawnLogContext(ImmutableMap.of("a", "1", "b", "2")); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| 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 spawnLogContext = createSpawnLogContext(ImmutableMap.of("a", "1", "b", "2")); |
| |
| spawnLogContext.logSpawn( |
| spawn, |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| // The spawn properties should override the default properties. |
| verify(outputStream) |
| .write( |
| 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 spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| spawn, |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| 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 spawnLogContext = createSpawnLogContext(); |
| |
| SpawnResult result = defaultSpawnResultBuilder().setCacheHit(true).build(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| result); |
| |
| verify(outputStream).write(defaultSpawnExecBuilder().setCacheHit(true).build()); |
| } |
| |
| @Test |
| public void testDigest() throws Exception { |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| SpawnResult result = defaultSpawnResultBuilder().setDigest(getDigest("abc")).build(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| result); |
| |
| verify(outputStream).write(defaultSpawnExecBuilder().setDigest(getDigest("abc")).build()); |
| } |
| |
| @Test |
| public void testTimeout() throws Exception { |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| /* timeout= */ Duration.ofSeconds(42), |
| defaultSpawnResult()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder().setTimeoutMillis(Duration.ofSeconds(42).toMillis()).build()); |
| } |
| |
| @Test |
| public void testSpawnMetrics() throws Exception { |
| SpawnMetrics metrics = SpawnMetrics.Builder.forLocalExec().setTotalTimeInMs(1).build(); |
| |
| SpawnLogContext spawnLogContext = createSpawnLogContext(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| defaultSpawnResultBuilder().setSpawnMetrics(metrics).build()); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .setMetrics(Protos.SpawnMetrics.newBuilder().setTotalTime(millisToProto(1))) |
| .build()); |
| } |
| |
| @Test |
| public void testStatus() throws Exception { |
| SpawnLogContext spawnLogContext = 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(); |
| |
| spawnLogContext.logSpawn( |
| defaultSpawn(), |
| createInputMetadataProvider(), |
| createInputMap(), |
| fs, |
| defaultTimeout(), |
| result); |
| |
| verify(outputStream) |
| .write( |
| defaultSpawnExecBuilder() |
| .setExitCode(37) |
| .setStatus(Status.NON_ZERO_EXIT.toString()) |
| .build()); |
| } |
| |
| private static Duration defaultTimeout() { |
| return Duration.ZERO; |
| } |
| |
| private static SpawnBuilder defaultSpawnBuilder() { |
| return new SpawnBuilder("cmd", "--opt"); |
| } |
| |
| private static Spawn defaultSpawn() { |
| return defaultSpawnBuilder().build(); |
| } |
| |
| private static SpawnResult.Builder defaultSpawnResultBuilder() { |
| return new SpawnResult.Builder().setRunnerName("runner").setStatus(Status.SUCCESS); |
| } |
| |
| private static SpawnResult defaultSpawnResult() { |
| return defaultSpawnResultBuilder().build(); |
| } |
| |
| private 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()); |
| } |
| |
| private static SpawnExec defaultSpawnExec() { |
| return defaultSpawnExecBuilder().build(); |
| } |
| |
| private static InputMetadataProvider createInputMetadataProvider(Artifact... artifacts) |
| throws Exception { |
| ImmutableMap.Builder<ActionInput, FileArtifactValue> builder = ImmutableMap.builder(); |
| 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 { |
| builder.put(artifact, FileArtifactValue.createForTesting(artifact)); |
| } |
| } |
| return new StaticInputMetadataProvider(builder.buildOrThrow()); |
| } |
| |
| private static SortedMap<PathFragment, ActionInput> createInputMap(Artifact... artifacts) |
| throws Exception { |
| ImmutableSortedMap.Builder<PathFragment, ActionInput> builder = |
| ImmutableSortedMap.naturalOrder(); |
| for (Artifact artifact : artifacts) { |
| if (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(artifact.getExecPath(), artifact); |
| } |
| } |
| return builder.buildOrThrow(); |
| } |
| |
| private static TreeArtifactValue createTreeArtifactValue(Artifact tree) throws Exception { |
| checkState(tree.isTreeArtifact()); |
| TreeArtifactValue.Builder builder = TreeArtifactValue.newBuilder((SpecialArtifact) tree); |
| TreeArtifactValue.visitTree( |
| tree.getPath(), |
| (parentRelativePath, type) -> { |
| if (type.equals(Dirent.Type.DIRECTORY)) { |
| return; |
| } |
| TreeFileArtifact child = |
| TreeFileArtifact.createTreeOutput((SpecialArtifact) tree, parentRelativePath); |
| builder.putChild(child, FileArtifactValue.createForTesting(child)); |
| }); |
| return builder.build(); |
| } |
| |
| private SpawnLogContext createSpawnLogContext() { |
| return createSpawnLogContext(ImmutableSortedMap.of()); |
| } |
| |
| private SpawnLogContext createSpawnLogContext(ImmutableMap<String, String> platformProperties) { |
| RemoteOptions remoteOptions = Options.getDefaults(RemoteOptions.class); |
| remoteOptions.remoteDefaultExecProperties = platformProperties.entrySet().asList(); |
| |
| return new SpawnLogContext( |
| execRoot.asFragment(), |
| outputStream, |
| Options.getDefaults(ExecutionOptions.class), |
| remoteOptions, |
| DigestHashFunction.SHA256, |
| SyscallCache.NO_CACHE); |
| } |
| |
| private Digest getDigest(String content) { |
| return Digest.newBuilder() |
| .setHash(digestHashFunction.getHashFunction().hashString(content, UTF_8).toString()) |
| .setSizeBytes(Utf8.encodedLength(content)) |
| .setHashFunctionName(digestHashFunction.toString()) |
| .build(); |
| } |
| |
| private static void writeFile(Artifact artifact, String contents) throws IOException { |
| writeFile(artifact.getPath(), contents); |
| } |
| |
| private static void writeFile(Path path, String contents) throws IOException { |
| path.getParentDirectory().createDirectoryAndParents(); |
| FileSystemUtils.writeContent(path, UTF_8, contents); |
| } |
| } |