| // 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.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 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()); |
| } |
| } |