blob: 3c9685dc2019fbc9ce466e8166f6d37109c333c6 [file] [log] [blame]
// Copyright 2022 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.runtime;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import com.github.luben.zstd.ZstdInputStream;
import com.github.luben.zstd.ZstdOutputStream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.ActionCompletionEvent;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
import com.google.devtools.build.lib.actions.DiscoveredInputsEvent;
import com.google.devtools.build.lib.actions.ExecutionGraph;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.actions.SimpleSpawn;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnExecutedEvent;
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.bugreport.BugReporter;
import com.google.devtools.build.lib.buildtool.BuildResult;
import com.google.devtools.build.lib.buildtool.BuildResult.BuildToolLogCollection;
import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
import com.google.devtools.build.lib.clock.BlazeClock;
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.util.FakeOwner;
import com.google.devtools.build.lib.exec.util.SpawnBuilder;
import com.google.devtools.build.lib.runtime.ExecutionGraphModule.ActionDumpWriter;
import com.google.devtools.build.lib.runtime.ExecutionGraphModule.DependencyInfo;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
/** Unit tests for {@link ExecutionGraphModule}. */
@RunWith(TestParameterInjector.class)
public class ExecutionGraphModuleTest extends FoundationTestCase {
private ExecutionGraphModule module;
private ArtifactRoot artifactRoot;
@Before
public void createModule() {
module = new ExecutionGraphModule();
}
@Before
public final void initializeRoots() throws Exception {
artifactRoot = ArtifactRoot.asDerivedRoot(scratch.resolve("/"), RootType.Output, "output");
}
private static ImmutableList<ExecutionGraph.Node> parse(ByteArrayOutputStream buffer)
throws IOException {
byte[] data = buffer.toByteArray();
try (InputStream in = new ZstdInputStream(new ByteArrayInputStream(data))) {
ImmutableList.Builder<ExecutionGraph.Node> nodeListBuilder = new ImmutableList.Builder<>();
ExecutionGraph.Node node;
while ((node = ExecutionGraph.Node.parseDelimitedFrom(in)) != null) {
nodeListBuilder.add(node);
}
return nodeListBuilder.build();
}
}
@Test
public void testOneSpawn() throws IOException {
UUID uuid = UUID.randomUUID();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Spawn spawn =
new SimpleSpawn(
new FakeOwnerWithPrimaryOutput(
"Mnemonic", "Progress message", "//foo", "output/foo/out"),
ImmutableList.of("cmd"),
ImmutableMap.of("env", "value"),
ImmutableMap.of("exec", "value"),
/* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* outputs= */ ImmutableSet.of(ActionInputHelper.fromPath("output/foo/out")),
ResourceSet.ZERO);
SpawnResult result =
new SpawnResult.Builder()
.setRunnerName("local")
.setStatus(Status.SUCCESS)
.setExitCode(0)
.setSpawnMetrics(
SpawnMetrics.Builder.forLocalExec()
.setTotalTime(Duration.ofMillis(1234L))
.setExecutionWallTime(Duration.ofMillis(2345L))
.setProcessOutputsTime(Duration.ofMillis(3456L))
.build())
.build();
startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
Instant startTimeInstant = Instant.now();
module.spawnExecuted(new SpawnExecutedEvent(spawn, result, startTimeInstant));
module.buildComplete(
new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
assertThat(nodes).hasSize(1);
assertThat(nodes.get(0).getTargetLabel()).isEqualTo("//foo:foo");
assertThat(nodes.get(0).getMnemonic()).isEqualTo("Mnemonic");
assertThat(nodes.get(0).getMetrics().getDurationMillis()).isEqualTo(1234L);
assertThat(nodes.get(0).getMetrics().getFetchMillis()).isEqualTo(0);
assertThat(nodes.get(0).getMetrics().getProcessOutputsMillis()).isEqualTo(3456);
assertThat(nodes.get(0).getMetrics().getStartTimestampMillis())
.isEqualTo(startTimeInstant.toEpochMilli());
assertThat(nodes.get(0).getIndex()).isEqualTo(0);
assertThat(nodes.get(0).getDependentIndexList()).isEmpty();
}
@Test
public void testSpawnWithDiscoverInputs() throws IOException {
UUID uuid = UUID.randomUUID();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Spawn spawn =
new SimpleSpawn(
new FakeOwnerWithPrimaryOutput(
"Mnemonic", "Progress message", "//foo", "output/foo/out"),
ImmutableList.of("cmd"),
ImmutableMap.of("env", "value"),
ImmutableMap.of("exec", "value"),
/* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* outputs= */ ImmutableSet.of(createOutputArtifact("output/foo/out")),
ResourceSet.ZERO);
SpawnResult result =
new SpawnResult.Builder()
.setRunnerName("local")
.setStatus(Status.SUCCESS)
.setExitCode(0)
.setSpawnMetrics(
SpawnMetrics.Builder.forLocalExec()
.setTotalTime(Duration.ofMillis(1234L))
.setExecutionWallTime(Duration.ofMillis(2345L))
.setProcessOutputsTime(Duration.ofMillis(3456L))
.setParseTime(Duration.ofMillis(2000))
.build())
.build();
startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
Instant startTimeInstant = Instant.ofEpochMilli(999888777L);
module.discoverInputs(
new DiscoveredInputsEvent(
SpawnMetrics.Builder.forOtherExec()
.setParseTime(Duration.ofMillis(987))
.setTotalTime(Duration.ofMillis(987))
.build(),
new ActionsTestUtil.NullAction(createOutputArtifact("output/foo/out")),
0));
module.spawnExecuted(new SpawnExecutedEvent(spawn, result, startTimeInstant));
module.buildComplete(
new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
ExecutionGraph.Metrics metrics = nodes.get(0).getMetrics();
assertThat(metrics.getDurationMillis()).isEqualTo(2221);
assertThat(metrics.getFetchMillis()).isEqualTo(0);
assertThat(metrics.getProcessMillis()).isEqualTo(2345);
assertThat(metrics.getProcessOutputsMillis()).isEqualTo(3456);
assertThat(metrics.getParseMillis()).isEqualTo(2000);
assertThat(metrics.getDiscoverInputsMillis()).isEqualTo(987);
}
@Test
public void actionDepsWithThreeSpawns() throws IOException {
UUID uuid = UUID.randomUUID();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ActionInput out1 = ActionInputHelper.fromPath("output/foo/out1");
ActionInput out2 = ActionInputHelper.fromPath("output/foo/out2");
ActionInput outTop = ActionInputHelper.fromPath("output/foo/out.top");
Spawn spawnOut1 =
new SimpleSpawn(
new FakeOwnerWithPrimaryOutput(
"Mnemonic", "Progress message", "//foo", out1.getExecPathString()),
ImmutableList.of("cmd"),
ImmutableMap.of("env", "value"),
ImmutableMap.of("exec", "value"),
/* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* outputs= */ ImmutableSet.of(out1),
ResourceSet.ZERO);
Spawn spawnOut2 =
new SimpleSpawn(
new FakeOwnerWithPrimaryOutput(
"Mnemonic", "Progress message", "//foo", out2.getExecPathString()),
ImmutableList.of("cmd"),
ImmutableMap.of("env", "value"),
ImmutableMap.of("exec", "value"),
/* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* outputs= */ ImmutableSet.of(out2),
ResourceSet.ZERO);
Spawn spawnTop =
new SimpleSpawn(
new FakeOwnerWithPrimaryOutput(
"Mnemonic", "Progress message", "//foo", outTop.getExecPathString()),
ImmutableList.of("cmd"),
ImmutableMap.of("env", "value"),
ImmutableMap.of("exec", "value"),
/* inputs= */ NestedSetBuilder.create(Order.COMPILE_ORDER, out1, out2),
/* outputs= */ ImmutableSet.of(outTop),
ResourceSet.ZERO);
SpawnResult result =
new SpawnResult.Builder()
.setRunnerName("local")
.setStatus(Status.SUCCESS)
.setExitCode(0)
.setSpawnMetrics(
SpawnMetrics.Builder.forLocalExec()
.setTotalTime(Duration.ofMillis(1234L))
.setExecutionWallTime(Duration.ofMillis(2345L))
.setProcessOutputsTime(Duration.ofMillis(3456L))
.build())
.build();
startLogging(eventBus, uuid, buffer, DependencyInfo.ALL);
Instant startTimeInstant = Instant.now();
module.spawnExecuted(new SpawnExecutedEvent(spawnOut1, result, startTimeInstant));
module.spawnExecuted(new SpawnExecutedEvent(spawnOut2, result, startTimeInstant));
module.spawnExecuted(new SpawnExecutedEvent(spawnTop, result, startTimeInstant));
module.buildComplete(
new BuildCompleteEvent(new BuildResult(startTimeInstant.plusMillis(1000).toEpochMilli())));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
assertThat(nodes).hasSize(3);
assertThat(nodes.get(0).getIndex()).isEqualTo(0);
assertThat(nodes.get(0).getDependentIndexList()).isEmpty();
assertThat(nodes.get(1).getIndex()).isEqualTo(1);
assertThat(nodes.get(1).getDependentIndexList()).isEmpty();
assertThat(nodes.get(2).getIndex()).isEqualTo(2);
assertThat(nodes.get(2).getDependentIndexList()).containsExactly(0, 1);
}
private enum FailingOutputStreamFactory {
CLOSE {
@Override
public ZstdOutputStream get() throws IOException {
return new ZstdOutputStream(OutputStream.nullOutputStream()) {
@Override
public synchronized void close() throws IOException {
throw new IOException("Simulated close failure");
}
};
}
},
/** Called from {@link com.google.protobuf.CodedOutputStream#flush}. */
WRITE {
@Override
public ZstdOutputStream get() throws IOException {
return new ZstdOutputStream(OutputStream.nullOutputStream()) {
@Override
public synchronized void write(byte[] b, int off, int len) throws IOException {
throw new IOException("oh no!");
}
};
}
};
abstract ZstdOutputStream get() throws IOException;
}
/** Regression test for b/218721483. */
@Test(timeout = 30_000)
public void failureInOutputDoesNotHang(
@TestParameter FailingOutputStreamFactory failingOutputStream) {
UUID uuid = UUID.randomUUID();
ActionDumpWriter writer =
new ActionDumpWriter(
BugReporter.defaultInstance(),
/*localLockFreeOutputEnabled=*/ false,
OutputStream.nullOutputStream(),
uuid,
DependencyInfo.NONE,
-1) {
@Override
protected void updateLogs(BuildToolLogCollection logs) {}
@Override
protected ZstdOutputStream createCompressingOutputStream() throws IOException {
return failingOutputStream.get();
}
};
module.setWriter(writer);
eventBus.register(module);
Instant startTimeInstant = Instant.now();
eventBus.post(new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
}
private void startLogging(
EventBus eventBus, UUID uuid, OutputStream buffer, DependencyInfo depType) {
startLogging(
eventBus,
BugReporter.defaultInstance(),
/*localLockFreeOutputEnabled=*/ false,
uuid,
buffer,
depType);
}
private void startLogging(
EventBus eventBus,
BugReporter bugReporter,
boolean localLockFreeOutputEnabled,
UUID uuid,
OutputStream buffer,
DependencyInfo depType) {
ActionDumpWriter writer =
new ActionDumpWriter(bugReporter, localLockFreeOutputEnabled, buffer, uuid, depType, -1) {
@Override
protected void updateLogs(BuildToolLogCollection logs) {}
};
module.setWriter(writer);
eventBus.register(module);
}
@Test
public void shutDownWithoutStartTolerated() {
eventBus.register(module);
Instant startTimeInstant = Instant.now();
// Doesn't crash.
eventBus.post(new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
}
@Test
public void testSpawnWithNullOwnerLabel() throws IOException {
UUID uuid = UUID.randomUUID();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Spawn spawn =
new SimpleSpawn(
new FakeOwnerWithPrimaryOutput(
"Mnemonic", "Progress message", "//unused:label", "output/foo/out") {
@Override
public ActionOwner getOwner() {
return ActionOwner.SYSTEM_ACTION_OWNER;
}
},
ImmutableList.of("cmd"),
ImmutableMap.of("env", "value"),
ImmutableMap.of("exec", "value"),
/* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* outputs= */ ImmutableSet.of(ActionInputHelper.fromPath("output/foo/out")),
ResourceSet.ZERO);
SpawnResult result =
new SpawnResult.Builder()
.setRunnerName("local")
.setStatus(Status.SUCCESS)
.setExitCode(0)
.setSpawnMetrics(
SpawnMetrics.Builder.forLocalExec()
.setTotalTime(Duration.ofMillis(1234L))
.setExecutionWallTime(Duration.ofMillis(2345L))
.setProcessOutputsTime(Duration.ofMillis(3456L))
.build())
.build();
startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
Instant startTimeInstant = Instant.now();
module.spawnExecuted(new SpawnExecutedEvent(spawn, result, startTimeInstant));
module.buildComplete(
new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
assertThat(nodes).hasSize(1);
assertThat(nodes.get(0).getTargetLabel()).isEmpty();
}
@Test
public void spawnAndAction_withSameOutputs() throws Exception {
var buffer = new ByteArrayOutputStream();
startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
var options = new ExecutionGraphModule.ExecutionGraphOptions();
options.logMissedActions = true;
module.setOptions(options);
module.spawnExecuted(
new SpawnExecutedEvent(
new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build(),
createRemoteSpawnResult(Duration.ofMillis(200)),
Instant.ofEpochMilli(100)));
module.actionComplete(
new ActionCompletionEvent(
0, new ActionsTestUtil.NullAction(createOutputArtifact("foo/out")), null));
module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
assertThat(parse(buffer))
.containsExactly(
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(0)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(100)
.setDurationMillis(200)
.setOtherMillis(200))
.setRunner("remote")
.build());
}
@Test
public void spawnAndAction_withDifferentOutputs() throws Exception {
var buffer = new ByteArrayOutputStream();
startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
var options = new ExecutionGraphModule.ExecutionGraphOptions();
options.logMissedActions = true;
module.setOptions(options);
var nanosToMillis = BlazeClock.createNanosToMillisSinceEpochConverter();
module.setNanosToMillis(nanosToMillis);
module.spawnExecuted(
new SpawnExecutedEvent(
new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build(),
createRemoteSpawnResult(Duration.ofMillis(200)),
Instant.ofEpochMilli(100)));
var action = new ActionsTestUtil.NullAction(createOutputArtifact("bar/out"));
module.actionComplete(new ActionCompletionEvent(0, action, null));
module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
assertThat(parse(buffer))
.containsExactly(
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(0)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(100)
.setDurationMillis(200)
.setOtherMillis(200))
.setRunner("remote")
.build(),
executionGraphNodeBuilderForAction(action)
.setIndex(1)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(nanosToMillis.toEpochMillis(0)))
.build());
}
@Test
public void multipleSpawnsWithSameOutput_recordsBothSpawnsWithRetry() throws Exception {
var buffer = new ByteArrayOutputStream();
startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
SpawnResult localResult = createLocalSpawnResult(Duration.ofMillis(100));
SpawnResult remoteResult = createRemoteSpawnResult(Duration.ofMillis(200));
Spawn spawn =
new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build();
module.spawnExecuted(new SpawnExecutedEvent(spawn, localResult, Instant.EPOCH));
module.spawnExecuted(new SpawnExecutedEvent(spawn, remoteResult, Instant.ofEpochMilli(100)));
module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
assertThat(nodes)
.containsExactly(
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(0)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(0)
.setDurationMillis(100)
.setOtherMillis(100))
.setRunner("local")
.build(),
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(1)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(100)
.setDurationMillis(200)
.setOtherMillis(200))
.setRunner("remote")
.setRetryOf(0)
.build())
.inOrder();
}
enum LocalLockFreeOutput {
LOCAL_LOCK_FREE_OUTPUT_ENABLED(/*optionValue=*/ true) {
@Override
void assertBugReport(BugReporter bugReporter) {
verify(bugReporter, never()).sendNonFatalBugReport(any());
}
},
LOCAL_LOCK_FREE_OUTPUT_DISABLED(/*optionValue=*/ false) {
@Override
void assertBugReport(BugReporter bugReporter) {
var captor = ArgumentCaptor.forClass(Exception.class);
verify(bugReporter).sendNonFatalBugReport(captor.capture());
assertThat(captor.getValue())
.hasMessageThat()
.contains("Multiple spawns produced 'output/foo/out' with overlapping execution time.");
}
};
LocalLockFreeOutput(boolean optionValue) {
this.optionValue = optionValue;
}
private final boolean optionValue;
abstract void assertBugReport(BugReporter bugReporter);
}
@Test
public void multipleSpawnsWithSameOutput_overlapping_recordsBothSpawnsWithoutRetry(
@TestParameter LocalLockFreeOutput localLockFreeOutput) throws Exception {
var buffer = new ByteArrayOutputStream();
BugReporter bugReporter = mock(BugReporter.class);
startLogging(
eventBus,
bugReporter,
localLockFreeOutput.optionValue,
UUID.randomUUID(),
buffer,
DependencyInfo.ALL);
SpawnResult localResult = createLocalSpawnResult(Duration.ofMillis(100));
SpawnResult remoteResult = createRemoteSpawnResult(Duration.ofMillis(200));
Spawn spawn =
new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build();
module.spawnExecuted(new SpawnExecutedEvent(spawn, localResult, Instant.EPOCH));
module.spawnExecuted(new SpawnExecutedEvent(spawn, remoteResult, Instant.ofEpochMilli(10)));
module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
assertThat(nodes)
.containsExactly(
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(0)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(0)
.setDurationMillis(100)
.setOtherMillis(100))
.setRunner("local")
.build(),
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(1)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(10)
.setDurationMillis(200)
.setOtherMillis(200))
.setRunner("remote")
.build())
.inOrder();
localLockFreeOutput.assertBugReport(bugReporter);
}
@Test
public void multipleSpawnsWithSameOutput_overlapping_ignoresSecondSpawnForDependencies()
throws Exception {
var buffer = new ByteArrayOutputStream();
startLogging(
eventBus,
BugReporter.defaultInstance(),
/*localLockFreeOutputEnabled=*/ true,
UUID.randomUUID(),
buffer,
DependencyInfo.ALL);
SpawnResult localResult = createLocalSpawnResult(Duration.ofMillis(100));
SpawnResult remoteResult = createRemoteSpawnResult(Duration.ofMillis(200));
Artifact input = createOutputArtifact("foo/input");
Spawn spawn = new SpawnBuilder().withOwnerPrimaryOutput(input).build();
Spawn dependentSpawn =
new SpawnBuilder()
.withOwnerPrimaryOutput(createOutputArtifact("foo/output"))
.withInput(input)
.build();
SpawnResult dependentResult = createRemoteSpawnResult(Duration.ofMillis(300));
module.spawnExecuted(new SpawnExecutedEvent(spawn, localResult, Instant.EPOCH));
module.spawnExecuted(new SpawnExecutedEvent(spawn, remoteResult, Instant.ofEpochMilli(10)));
module.spawnExecuted(
new SpawnExecutedEvent(dependentSpawn, dependentResult, Instant.ofEpochMilli(300)));
module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
assertThat(nodes)
.containsExactly(
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(0)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(0)
.setDurationMillis(100)
.setOtherMillis(100))
.setRunner("local")
.build(),
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(1)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(10)
.setDurationMillis(200)
.setOtherMillis(200))
.setRunner("remote")
.build(),
executionGraphNodeBuilderForSpawnBuilderSpawn()
.setIndex(2)
.setMetrics(
ExecutionGraph.Metrics.newBuilder()
.setStartTimestampMillis(300)
.setDurationMillis(300)
.setOtherMillis(300))
.setRunner("remote")
.addDependentIndex(0)
.build())
.inOrder();
}
private class FakeOwnerWithPrimaryOutput extends FakeOwner {
private final String primaryOutput;
public FakeOwnerWithPrimaryOutput(
String mnemonic, String progressMessage, String ownerLabel, String primaryOutput) {
super(mnemonic, progressMessage, ownerLabel);
this.primaryOutput = primaryOutput;
}
@Override
public Artifact getPrimaryOutput() {
return ActionsTestUtil.createArtifactWithExecPath(
artifactRoot, PathFragment.create(primaryOutput));
}
}
private Artifact createOutputArtifact(String rootRelativePath) {
return ActionsTestUtil.createArtifactWithExecPath(
artifactRoot, artifactRoot.getExecPath().getRelative(rootRelativePath));
}
private SpawnResult createLocalSpawnResult(Duration totalTime) {
return new SpawnResult.Builder()
.setRunnerName("local")
.setStatus(Status.SUCCESS)
.setExitCode(0)
.setSpawnMetrics(SpawnMetrics.Builder.forLocalExec().setTotalTime(totalTime).build())
.build();
}
private SpawnResult createRemoteSpawnResult(Duration totalTime) {
return new SpawnResult.Builder()
.setRunnerName("remote")
.setStatus(Status.SUCCESS)
.setExitCode(0)
.setSpawnMetrics(SpawnMetrics.Builder.forRemoteExec().setTotalTime(totalTime).build())
.build();
}
/**
* Creates a {@link ExecutionGraph.Node.Builder} with pre-populated defaults for spawns created
* using {@link SpawnBuilder}.
*/
private ExecutionGraph.Node.Builder executionGraphNodeBuilderForSpawnBuilderSpawn() {
return ExecutionGraph.Node.newBuilder()
.setDescription("action 'progress message'")
.setTargetLabel("//dummy:label")
.setMnemonic("Mnemonic")
// This comes from SpawnResult.Builder, which defaults to an empty string.
.setRunnerSubtype("");
}
/**
* Creates a {@link ExecutionGraph.Node.Builder} with pre-populated defaults for action events.
*/
private ExecutionGraph.Node.Builder executionGraphNodeBuilderForAction(Action action) {
return ExecutionGraph.Node.newBuilder()
.setDescription(action.prettyPrint())
.setTargetLabel(action.getOwner().getLabel().toString())
.setMnemonic(action.getMnemonic());
}
}