blob: c6ff72b83fdae709496f27ec258ba29c0ba329fd [file] [log] [blame]
Googler7f326872022-09-20 11:19:43 -07001// Copyright 2022 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.runtime;
15
16import static com.google.common.truth.Truth.assertThat;
17import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
Googler7f326872022-09-20 11:19:43 -070018import static org.mockito.ArgumentMatchers.any;
19import static org.mockito.Mockito.mock;
20import static org.mockito.Mockito.never;
21import static org.mockito.Mockito.verify;
22
23import com.github.luben.zstd.ZstdInputStream;
24import com.github.luben.zstd.ZstdOutputStream;
25import com.google.common.collect.ImmutableList;
26import com.google.common.collect.ImmutableMap;
27import com.google.common.collect.ImmutableSet;
28import com.google.common.eventbus.EventBus;
Googler034f0242022-09-21 11:34:24 -070029import com.google.devtools.build.lib.actions.Action;
30import com.google.devtools.build.lib.actions.ActionCompletionEvent;
Googler7f326872022-09-20 11:19:43 -070031import com.google.devtools.build.lib.actions.ActionInput;
32import com.google.devtools.build.lib.actions.ActionInputHelper;
33import com.google.devtools.build.lib.actions.ActionOwner;
34import com.google.devtools.build.lib.actions.Artifact;
35import com.google.devtools.build.lib.actions.ArtifactRoot;
36import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
Googler0e099d92023-01-09 10:58:55 -080037import com.google.devtools.build.lib.actions.DiscoveredInputsEvent;
Googler7f326872022-09-20 11:19:43 -070038import com.google.devtools.build.lib.actions.ExecutionGraph;
39import com.google.devtools.build.lib.actions.ResourceSet;
40import com.google.devtools.build.lib.actions.SimpleSpawn;
41import com.google.devtools.build.lib.actions.Spawn;
42import com.google.devtools.build.lib.actions.SpawnExecutedEvent;
43import com.google.devtools.build.lib.actions.SpawnMetrics;
44import com.google.devtools.build.lib.actions.SpawnResult;
45import com.google.devtools.build.lib.actions.SpawnResult.Status;
46import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
47import com.google.devtools.build.lib.bugreport.BugReporter;
48import com.google.devtools.build.lib.buildtool.BuildResult;
49import com.google.devtools.build.lib.buildtool.BuildResult.BuildToolLogCollection;
50import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
Googler034f0242022-09-21 11:34:24 -070051import com.google.devtools.build.lib.clock.BlazeClock;
Googler7f326872022-09-20 11:19:43 -070052import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
53import com.google.devtools.build.lib.collect.nestedset.Order;
Googler17f678b2024-01-31 06:39:16 -080054import com.google.devtools.build.lib.exec.util.FakeActionInputFileCache;
Googler7f326872022-09-20 11:19:43 -070055import com.google.devtools.build.lib.exec.util.FakeOwner;
56import com.google.devtools.build.lib.exec.util.SpawnBuilder;
Googlera2177dc2022-09-22 10:18:10 -070057import com.google.devtools.build.lib.runtime.ExecutionGraphModule.ActionDumpWriter;
58import com.google.devtools.build.lib.runtime.ExecutionGraphModule.DependencyInfo;
Googler7f326872022-09-20 11:19:43 -070059import com.google.devtools.build.lib.testutil.FoundationTestCase;
Googler7f326872022-09-20 11:19:43 -070060import com.google.devtools.build.lib.vfs.PathFragment;
61import com.google.testing.junit.testparameterinjector.TestParameter;
62import com.google.testing.junit.testparameterinjector.TestParameterInjector;
63import java.io.ByteArrayInputStream;
64import java.io.ByteArrayOutputStream;
65import java.io.IOException;
66import java.io.InputStream;
67import java.io.OutputStream;
Googler7f326872022-09-20 11:19:43 -070068import java.time.Instant;
69import java.util.UUID;
70import org.junit.Before;
71import org.junit.Test;
72import org.junit.runner.RunWith;
73import org.mockito.ArgumentCaptor;
74
Googlera2177dc2022-09-22 10:18:10 -070075/** Unit tests for {@link ExecutionGraphModule}. */
Googler7f326872022-09-20 11:19:43 -070076@RunWith(TestParameterInjector.class)
Googlera2177dc2022-09-22 10:18:10 -070077public class ExecutionGraphModuleTest extends FoundationTestCase {
78 private ExecutionGraphModule module;
Googler7f326872022-09-20 11:19:43 -070079 private ArtifactRoot artifactRoot;
80
81 @Before
82 public void createModule() {
Googlera2177dc2022-09-22 10:18:10 -070083 module = new ExecutionGraphModule();
Googler7f326872022-09-20 11:19:43 -070084 }
85
86 @Before
87 public final void initializeRoots() throws Exception {
88 artifactRoot = ArtifactRoot.asDerivedRoot(scratch.resolve("/"), RootType.Output, "output");
89 }
90
91 private static ImmutableList<ExecutionGraph.Node> parse(ByteArrayOutputStream buffer)
92 throws IOException {
93 byte[] data = buffer.toByteArray();
94 try (InputStream in = new ZstdInputStream(new ByteArrayInputStream(data))) {
95 ImmutableList.Builder<ExecutionGraph.Node> nodeListBuilder = new ImmutableList.Builder<>();
96 ExecutionGraph.Node node;
97 while ((node = ExecutionGraph.Node.parseDelimitedFrom(in)) != null) {
98 nodeListBuilder.add(node);
99 }
100 return nodeListBuilder.build();
101 }
102 }
103
104 @Test
105 public void testOneSpawn() throws IOException {
Googler7f326872022-09-20 11:19:43 -0700106 UUID uuid = UUID.randomUUID();
107 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
108 Spawn spawn =
109 new SimpleSpawn(
110 new FakeOwnerWithPrimaryOutput(
111 "Mnemonic", "Progress message", "//foo", "output/foo/out"),
112 ImmutableList.of("cmd"),
113 ImmutableMap.of("env", "value"),
114 ImmutableMap.of("exec", "value"),
115 /* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
116 /* outputs= */ ImmutableSet.of(ActionInputHelper.fromPath("output/foo/out")),
117 ResourceSet.ZERO);
118 SpawnResult result =
119 new SpawnResult.Builder()
120 .setRunnerName("local")
121 .setStatus(Status.SUCCESS)
122 .setExitCode(0)
123 .setSpawnMetrics(
124 SpawnMetrics.Builder.forLocalExec()
Googler9961a752023-02-23 05:21:53 -0800125 .setTotalTimeInMs(1234)
126 .setExecutionWallTimeInMs(2345)
127 .setProcessOutputsTimeInMs(3456)
Googler7f326872022-09-20 11:19:43 -0700128 .build())
129 .build();
130 startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
131 Instant startTimeInstant = Instant.now();
Googler17f678b2024-01-31 06:39:16 -0800132 module.spawnExecuted(
133 new SpawnExecutedEvent(spawn, new FakeActionInputFileCache(), result, startTimeInstant));
Googler7f326872022-09-20 11:19:43 -0700134 module.buildComplete(
135 new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
136
137 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
138 assertThat(nodes).hasSize(1);
139 assertThat(nodes.get(0).getTargetLabel()).isEqualTo("//foo:foo");
140 assertThat(nodes.get(0).getMnemonic()).isEqualTo("Mnemonic");
141 assertThat(nodes.get(0).getMetrics().getDurationMillis()).isEqualTo(1234L);
142 assertThat(nodes.get(0).getMetrics().getFetchMillis()).isEqualTo(0);
143 assertThat(nodes.get(0).getMetrics().getProcessOutputsMillis()).isEqualTo(3456);
Googler034f0242022-09-21 11:34:24 -0700144 assertThat(nodes.get(0).getMetrics().getStartTimestampMillis())
145 .isEqualTo(startTimeInstant.toEpochMilli());
Googler7f326872022-09-20 11:19:43 -0700146 assertThat(nodes.get(0).getIndex()).isEqualTo(0);
147 assertThat(nodes.get(0).getDependentIndexList()).isEmpty();
148 }
149
150 @Test
Googler0e099d92023-01-09 10:58:55 -0800151 public void testSpawnWithDiscoverInputs() throws IOException {
152 UUID uuid = UUID.randomUUID();
153 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
154 Spawn spawn =
155 new SimpleSpawn(
156 new FakeOwnerWithPrimaryOutput(
157 "Mnemonic", "Progress message", "//foo", "output/foo/out"),
158 ImmutableList.of("cmd"),
159 ImmutableMap.of("env", "value"),
160 ImmutableMap.of("exec", "value"),
161 /* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
162 /* outputs= */ ImmutableSet.of(createOutputArtifact("output/foo/out")),
163 ResourceSet.ZERO);
164 SpawnResult result =
165 new SpawnResult.Builder()
166 .setRunnerName("local")
167 .setStatus(Status.SUCCESS)
168 .setExitCode(0)
169 .setSpawnMetrics(
170 SpawnMetrics.Builder.forLocalExec()
Googler9961a752023-02-23 05:21:53 -0800171 .setTotalTimeInMs(1234)
172 .setExecutionWallTimeInMs(2345)
173 .setProcessOutputsTimeInMs(3456)
174 .setParseTimeInMs(2000)
Googler0e099d92023-01-09 10:58:55 -0800175 .build())
176 .build();
177 startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
178 Instant startTimeInstant = Instant.ofEpochMilli(999888777L);
179 module.discoverInputs(
180 new DiscoveredInputsEvent(
Googler9961a752023-02-23 05:21:53 -0800181 SpawnMetrics.Builder.forOtherExec().setParseTimeInMs(987).setTotalTimeInMs(987).build(),
Googler0e099d92023-01-09 10:58:55 -0800182 new ActionsTestUtil.NullAction(createOutputArtifact("output/foo/out")),
183 0));
Googler17f678b2024-01-31 06:39:16 -0800184 module.spawnExecuted(
185 new SpawnExecutedEvent(spawn, new FakeActionInputFileCache(), result, startTimeInstant));
Googler0e099d92023-01-09 10:58:55 -0800186 module.buildComplete(
187 new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
188
189 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
190 ExecutionGraph.Metrics metrics = nodes.get(0).getMetrics();
191 assertThat(metrics.getDurationMillis()).isEqualTo(2221);
192 assertThat(metrics.getFetchMillis()).isEqualTo(0);
193 assertThat(metrics.getProcessMillis()).isEqualTo(2345);
194 assertThat(metrics.getProcessOutputsMillis()).isEqualTo(3456);
195 assertThat(metrics.getParseMillis()).isEqualTo(2000);
196 assertThat(metrics.getDiscoverInputsMillis()).isEqualTo(987);
197 }
198
199 @Test
Googler7f326872022-09-20 11:19:43 -0700200 public void actionDepsWithThreeSpawns() throws IOException {
Googler7f326872022-09-20 11:19:43 -0700201 UUID uuid = UUID.randomUUID();
202 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
203
204 ActionInput out1 = ActionInputHelper.fromPath("output/foo/out1");
205 ActionInput out2 = ActionInputHelper.fromPath("output/foo/out2");
206 ActionInput outTop = ActionInputHelper.fromPath("output/foo/out.top");
207
208 Spawn spawnOut1 =
209 new SimpleSpawn(
210 new FakeOwnerWithPrimaryOutput(
211 "Mnemonic", "Progress message", "//foo", out1.getExecPathString()),
212 ImmutableList.of("cmd"),
213 ImmutableMap.of("env", "value"),
214 ImmutableMap.of("exec", "value"),
215 /* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
216 /* outputs= */ ImmutableSet.of(out1),
217 ResourceSet.ZERO);
218 Spawn spawnOut2 =
219 new SimpleSpawn(
220 new FakeOwnerWithPrimaryOutput(
221 "Mnemonic", "Progress message", "//foo", out2.getExecPathString()),
222 ImmutableList.of("cmd"),
223 ImmutableMap.of("env", "value"),
224 ImmutableMap.of("exec", "value"),
225 /* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
226 /* outputs= */ ImmutableSet.of(out2),
227 ResourceSet.ZERO);
228 Spawn spawnTop =
229 new SimpleSpawn(
230 new FakeOwnerWithPrimaryOutput(
231 "Mnemonic", "Progress message", "//foo", outTop.getExecPathString()),
232 ImmutableList.of("cmd"),
233 ImmutableMap.of("env", "value"),
234 ImmutableMap.of("exec", "value"),
235 /* inputs= */ NestedSetBuilder.create(Order.COMPILE_ORDER, out1, out2),
236 /* outputs= */ ImmutableSet.of(outTop),
237 ResourceSet.ZERO);
238 SpawnResult result =
239 new SpawnResult.Builder()
240 .setRunnerName("local")
241 .setStatus(Status.SUCCESS)
242 .setExitCode(0)
243 .setSpawnMetrics(
244 SpawnMetrics.Builder.forLocalExec()
Googler9961a752023-02-23 05:21:53 -0800245 .setTotalTimeInMs(1234)
246 .setExecutionWallTimeInMs(2345)
247 .setProcessOutputsTimeInMs(3456)
Googler7f326872022-09-20 11:19:43 -0700248 .build())
249 .build();
250 startLogging(eventBus, uuid, buffer, DependencyInfo.ALL);
251 Instant startTimeInstant = Instant.now();
Googler17f678b2024-01-31 06:39:16 -0800252 module.spawnExecuted(
253 new SpawnExecutedEvent(
254 spawnOut1, new FakeActionInputFileCache(), result, startTimeInstant));
255 module.spawnExecuted(
256 new SpawnExecutedEvent(
257 spawnOut2, new FakeActionInputFileCache(), result, startTimeInstant));
258 module.spawnExecuted(
259 new SpawnExecutedEvent(spawnTop, new FakeActionInputFileCache(), result, startTimeInstant));
Googler7f326872022-09-20 11:19:43 -0700260 module.buildComplete(
261 new BuildCompleteEvent(new BuildResult(startTimeInstant.plusMillis(1000).toEpochMilli())));
262
263 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
264 assertThat(nodes).hasSize(3);
265
266 assertThat(nodes.get(0).getIndex()).isEqualTo(0);
267 assertThat(nodes.get(0).getDependentIndexList()).isEmpty();
268
269 assertThat(nodes.get(1).getIndex()).isEqualTo(1);
270 assertThat(nodes.get(1).getDependentIndexList()).isEmpty();
271
272 assertThat(nodes.get(2).getIndex()).isEqualTo(2);
273 assertThat(nodes.get(2).getDependentIndexList()).containsExactly(0, 1);
274 }
275
276 private enum FailingOutputStreamFactory {
277 CLOSE {
278 @Override
279 public ZstdOutputStream get() throws IOException {
280 return new ZstdOutputStream(OutputStream.nullOutputStream()) {
281 @Override
282 public synchronized void close() throws IOException {
283 throw new IOException("Simulated close failure");
284 }
285 };
286 }
287 },
288 /** Called from {@link com.google.protobuf.CodedOutputStream#flush}. */
289 WRITE {
290 @Override
291 public ZstdOutputStream get() throws IOException {
292 return new ZstdOutputStream(OutputStream.nullOutputStream()) {
293 @Override
294 public synchronized void write(byte[] b, int off, int len) throws IOException {
295 throw new IOException("oh no!");
296 }
297 };
298 }
299 };
300
301 abstract ZstdOutputStream get() throws IOException;
302 }
303
304 /** Regression test for b/218721483. */
305 @Test(timeout = 30_000)
306 public void failureInOutputDoesNotHang(
307 @TestParameter FailingOutputStreamFactory failingOutputStream) {
Googler7f326872022-09-20 11:19:43 -0700308 UUID uuid = UUID.randomUUID();
309 ActionDumpWriter writer =
310 new ActionDumpWriter(
311 BugReporter.defaultInstance(),
Googler49731662023-03-21 01:37:10 -0700312 /* localLockFreeOutputEnabled= */ false,
313 /* logFileWriteEdges= */ false,
Googler7f326872022-09-20 11:19:43 -0700314 OutputStream.nullOutputStream(),
315 uuid,
316 DependencyInfo.NONE,
317 -1) {
318 @Override
319 protected void updateLogs(BuildToolLogCollection logs) {}
320
321 @Override
322 protected ZstdOutputStream createCompressingOutputStream() throws IOException {
323 return failingOutputStream.get();
324 }
325 };
326 module.setWriter(writer);
327 eventBus.register(module);
328
329 Instant startTimeInstant = Instant.now();
330 eventBus.post(new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
331 }
332
333 private void startLogging(
334 EventBus eventBus, UUID uuid, OutputStream buffer, DependencyInfo depType) {
335 startLogging(
336 eventBus,
337 BugReporter.defaultInstance(),
Googler49731662023-03-21 01:37:10 -0700338 /* localLockFreeOutputEnabled= */ false,
339 /* logFileWriteEdges= */ false,
Googler7f326872022-09-20 11:19:43 -0700340 uuid,
341 buffer,
342 depType);
343 }
344
345 private void startLogging(
346 EventBus eventBus,
347 BugReporter bugReporter,
348 boolean localLockFreeOutputEnabled,
Googler49731662023-03-21 01:37:10 -0700349 boolean logFileWriteEdges,
Googler7f326872022-09-20 11:19:43 -0700350 UUID uuid,
351 OutputStream buffer,
352 DependencyInfo depType) {
353 ActionDumpWriter writer =
Googler49731662023-03-21 01:37:10 -0700354 new ActionDumpWriter(
355 bugReporter, localLockFreeOutputEnabled, logFileWriteEdges, buffer, uuid, depType, -1) {
Googler7f326872022-09-20 11:19:43 -0700356 @Override
357 protected void updateLogs(BuildToolLogCollection logs) {}
358 };
359 module.setWriter(writer);
360 eventBus.register(module);
361 }
362
363 @Test
364 public void shutDownWithoutStartTolerated() {
365 eventBus.register(module);
366 Instant startTimeInstant = Instant.now();
367 // Doesn't crash.
368 eventBus.post(new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
369 }
370
371 @Test
372 public void testSpawnWithNullOwnerLabel() throws IOException {
Googler7f326872022-09-20 11:19:43 -0700373 UUID uuid = UUID.randomUUID();
374 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
375 Spawn spawn =
376 new SimpleSpawn(
377 new FakeOwnerWithPrimaryOutput(
378 "Mnemonic", "Progress message", "//unused:label", "output/foo/out") {
379 @Override
380 public ActionOwner getOwner() {
381 return ActionOwner.SYSTEM_ACTION_OWNER;
382 }
383 },
384 ImmutableList.of("cmd"),
385 ImmutableMap.of("env", "value"),
386 ImmutableMap.of("exec", "value"),
387 /* inputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
388 /* outputs= */ ImmutableSet.of(ActionInputHelper.fromPath("output/foo/out")),
389 ResourceSet.ZERO);
390 SpawnResult result =
391 new SpawnResult.Builder()
392 .setRunnerName("local")
393 .setStatus(Status.SUCCESS)
394 .setExitCode(0)
395 .setSpawnMetrics(
396 SpawnMetrics.Builder.forLocalExec()
Googler9961a752023-02-23 05:21:53 -0800397 .setTotalTimeInMs(1234)
398 .setExecutionWallTimeInMs(2345)
399 .setProcessOutputsTimeInMs(3456)
Googler7f326872022-09-20 11:19:43 -0700400 .build())
401 .build();
402 startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
403 Instant startTimeInstant = Instant.now();
Googler17f678b2024-01-31 06:39:16 -0800404 module.spawnExecuted(
405 new SpawnExecutedEvent(spawn, new FakeActionInputFileCache(), result, startTimeInstant));
Googler7f326872022-09-20 11:19:43 -0700406 module.buildComplete(
407 new BuildCompleteEvent(new BuildResult(startTimeInstant.toEpochMilli() + 1000)));
408
409 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
410 assertThat(nodes).hasSize(1);
411 assertThat(nodes.get(0).getTargetLabel()).isEmpty();
412 }
413
414 @Test
Googler034f0242022-09-21 11:34:24 -0700415 public void spawnAndAction_withSameOutputs() throws Exception {
Googler034f0242022-09-21 11:34:24 -0700416 var buffer = new ByteArrayOutputStream();
417 startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
Googlera2177dc2022-09-22 10:18:10 -0700418 var options = new ExecutionGraphModule.ExecutionGraphOptions();
Googler034f0242022-09-21 11:34:24 -0700419 module.setOptions(options);
420
421 module.spawnExecuted(
422 new SpawnExecutedEvent(
423 new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build(),
Googler17f678b2024-01-31 06:39:16 -0800424 new FakeActionInputFileCache(),
Googler9961a752023-02-23 05:21:53 -0800425 createRemoteSpawnResult(200),
Googler034f0242022-09-21 11:34:24 -0700426 Instant.ofEpochMilli(100)));
427 module.actionComplete(
428 new ActionCompletionEvent(
Googler17f678b2024-01-31 06:39:16 -0800429 0,
430 0,
431 new ActionsTestUtil.NullAction(createOutputArtifact("foo/out")),
432 new FakeActionInputFileCache(),
433 null));
Googler034f0242022-09-21 11:34:24 -0700434 module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
435
436 assertThat(parse(buffer))
437 .containsExactly(
438 executionGraphNodeBuilderForSpawnBuilderSpawn()
439 .setIndex(0)
440 .setMetrics(
441 ExecutionGraph.Metrics.newBuilder()
442 .setStartTimestampMillis(100)
443 .setDurationMillis(200)
444 .setOtherMillis(200))
445 .setRunner("remote")
Googlerc0b09962023-12-04 08:16:44 -0800446 .setRuleClass("dummy-target-kind")
Googler034f0242022-09-21 11:34:24 -0700447 .build());
448 }
449
450 @Test
451 public void spawnAndAction_withDifferentOutputs() throws Exception {
Googler034f0242022-09-21 11:34:24 -0700452 var buffer = new ByteArrayOutputStream();
453 startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
Googlera2177dc2022-09-22 10:18:10 -0700454 var options = new ExecutionGraphModule.ExecutionGraphOptions();
Googler034f0242022-09-21 11:34:24 -0700455 module.setOptions(options);
456 var nanosToMillis = BlazeClock.createNanosToMillisSinceEpochConverter();
457 module.setNanosToMillis(nanosToMillis);
458
459 module.spawnExecuted(
460 new SpawnExecutedEvent(
461 new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build(),
Googler17f678b2024-01-31 06:39:16 -0800462 new FakeActionInputFileCache(),
Googler9961a752023-02-23 05:21:53 -0800463 createRemoteSpawnResult(200),
Googler034f0242022-09-21 11:34:24 -0700464 Instant.ofEpochMilli(100)));
465 var action = new ActionsTestUtil.NullAction(createOutputArtifact("bar/out"));
Googler17f678b2024-01-31 06:39:16 -0800466 module.actionComplete(
467 new ActionCompletionEvent(0, 0, action, new FakeActionInputFileCache(), null));
Googler034f0242022-09-21 11:34:24 -0700468 module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
469
470 assertThat(parse(buffer))
471 .containsExactly(
472 executionGraphNodeBuilderForSpawnBuilderSpawn()
473 .setIndex(0)
474 .setMetrics(
475 ExecutionGraph.Metrics.newBuilder()
476 .setStartTimestampMillis(100)
477 .setDurationMillis(200)
478 .setOtherMillis(200))
Googlerc0b09962023-12-04 08:16:44 -0800479 .setRuleClass("dummy-target-kind")
Googler034f0242022-09-21 11:34:24 -0700480 .setRunner("remote")
481 .build(),
482 executionGraphNodeBuilderForAction(action)
483 .setIndex(1)
484 .setMetrics(
485 ExecutionGraph.Metrics.newBuilder()
486 .setStartTimestampMillis(nanosToMillis.toEpochMillis(0)))
Googlerc0b09962023-12-04 08:16:44 -0800487 .setRuleClass("dummy-kind")
Googler034f0242022-09-21 11:34:24 -0700488 .build());
489 }
490
491 @Test
Googler7f326872022-09-20 11:19:43 -0700492 public void multipleSpawnsWithSameOutput_recordsBothSpawnsWithRetry() throws Exception {
Googler7f326872022-09-20 11:19:43 -0700493 var buffer = new ByteArrayOutputStream();
494 startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
Googler9961a752023-02-23 05:21:53 -0800495 SpawnResult localResult = createLocalSpawnResult(100);
496 SpawnResult remoteResult = createRemoteSpawnResult(200);
Googler7f326872022-09-20 11:19:43 -0700497 Spawn spawn =
498 new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build();
499
Googler17f678b2024-01-31 06:39:16 -0800500 module.spawnExecuted(
501 new SpawnExecutedEvent(spawn, new FakeActionInputFileCache(), localResult, Instant.EPOCH));
502 module.spawnExecuted(
503 new SpawnExecutedEvent(
504 spawn, new FakeActionInputFileCache(), remoteResult, Instant.ofEpochMilli(100)));
Googler7f326872022-09-20 11:19:43 -0700505 module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
506
507 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
508 assertThat(nodes)
509 .containsExactly(
510 executionGraphNodeBuilderForSpawnBuilderSpawn()
511 .setIndex(0)
Googler034f0242022-09-21 11:34:24 -0700512 .setMetrics(
513 ExecutionGraph.Metrics.newBuilder()
Googler7f326872022-09-20 11:19:43 -0700514 .setStartTimestampMillis(0)
515 .setDurationMillis(100)
516 .setOtherMillis(100))
517 .setRunner("local")
518 .build(),
519 executionGraphNodeBuilderForSpawnBuilderSpawn()
520 .setIndex(1)
Googler034f0242022-09-21 11:34:24 -0700521 .setMetrics(
522 ExecutionGraph.Metrics.newBuilder()
523 .setStartTimestampMillis(100)
524 .setDurationMillis(200)
525 .setOtherMillis(200))
Googler7f326872022-09-20 11:19:43 -0700526 .setRunner("remote")
527 .setRetryOf(0)
528 .build())
529 .inOrder();
530 }
531
532 enum LocalLockFreeOutput {
533 LOCAL_LOCK_FREE_OUTPUT_ENABLED(/*optionValue=*/ true) {
534 @Override
535 void assertBugReport(BugReporter bugReporter) {
536 verify(bugReporter, never()).sendNonFatalBugReport(any());
537 }
538 },
539 LOCAL_LOCK_FREE_OUTPUT_DISABLED(/*optionValue=*/ false) {
540 @Override
541 void assertBugReport(BugReporter bugReporter) {
542 var captor = ArgumentCaptor.forClass(Exception.class);
543 verify(bugReporter).sendNonFatalBugReport(captor.capture());
544 assertThat(captor.getValue())
545 .hasMessageThat()
546 .contains("Multiple spawns produced 'output/foo/out' with overlapping execution time.");
547 }
548 };
549
550 LocalLockFreeOutput(boolean optionValue) {
551 this.optionValue = optionValue;
552 }
553
554 private final boolean optionValue;
555
556 abstract void assertBugReport(BugReporter bugReporter);
557 }
558
559 @Test
560 public void multipleSpawnsWithSameOutput_overlapping_recordsBothSpawnsWithoutRetry(
561 @TestParameter LocalLockFreeOutput localLockFreeOutput) throws Exception {
Googler7f326872022-09-20 11:19:43 -0700562 var buffer = new ByteArrayOutputStream();
563 BugReporter bugReporter = mock(BugReporter.class);
564 startLogging(
565 eventBus,
566 bugReporter,
567 localLockFreeOutput.optionValue,
Googler49731662023-03-21 01:37:10 -0700568 /* logFileWriteEdges= */ false,
Googler7f326872022-09-20 11:19:43 -0700569 UUID.randomUUID(),
570 buffer,
571 DependencyInfo.ALL);
Googler9961a752023-02-23 05:21:53 -0800572 SpawnResult localResult = createLocalSpawnResult(100);
573 SpawnResult remoteResult = createRemoteSpawnResult(200);
Googler7f326872022-09-20 11:19:43 -0700574 Spawn spawn =
575 new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build();
576
Googler17f678b2024-01-31 06:39:16 -0800577 module.spawnExecuted(
578 new SpawnExecutedEvent(spawn, new FakeActionInputFileCache(), localResult, Instant.EPOCH));
579 module.spawnExecuted(
580 new SpawnExecutedEvent(
581 spawn, new FakeActionInputFileCache(), remoteResult, Instant.ofEpochMilli(10)));
Googler7f326872022-09-20 11:19:43 -0700582 module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
583
584 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
585 assertThat(nodes)
586 .containsExactly(
587 executionGraphNodeBuilderForSpawnBuilderSpawn()
588 .setIndex(0)
Googler034f0242022-09-21 11:34:24 -0700589 .setMetrics(
590 ExecutionGraph.Metrics.newBuilder()
591 .setStartTimestampMillis(0)
592 .setDurationMillis(100)
593 .setOtherMillis(100))
Googler7f326872022-09-20 11:19:43 -0700594 .setRunner("local")
595 .build(),
596 executionGraphNodeBuilderForSpawnBuilderSpawn()
597 .setIndex(1)
Googler034f0242022-09-21 11:34:24 -0700598 .setMetrics(
599 ExecutionGraph.Metrics.newBuilder()
600 .setStartTimestampMillis(10)
601 .setDurationMillis(200)
602 .setOtherMillis(200))
Googler7f326872022-09-20 11:19:43 -0700603 .setRunner("remote")
604 .build())
605 .inOrder();
606 localLockFreeOutput.assertBugReport(bugReporter);
607 }
608
609 @Test
610 public void multipleSpawnsWithSameOutput_overlapping_ignoresSecondSpawnForDependencies()
611 throws Exception {
Googler7f326872022-09-20 11:19:43 -0700612 var buffer = new ByteArrayOutputStream();
613 startLogging(
614 eventBus,
615 BugReporter.defaultInstance(),
Googler49731662023-03-21 01:37:10 -0700616 /* localLockFreeOutputEnabled= */ true,
617 /* logFileWriteEdges= */ false,
Googler7f326872022-09-20 11:19:43 -0700618 UUID.randomUUID(),
619 buffer,
620 DependencyInfo.ALL);
Googler9961a752023-02-23 05:21:53 -0800621 SpawnResult localResult = createLocalSpawnResult(100);
622 SpawnResult remoteResult = createRemoteSpawnResult(200);
Googler7f326872022-09-20 11:19:43 -0700623 Artifact input = createOutputArtifact("foo/input");
624 Spawn spawn = new SpawnBuilder().withOwnerPrimaryOutput(input).build();
625 Spawn dependentSpawn =
626 new SpawnBuilder()
627 .withOwnerPrimaryOutput(createOutputArtifact("foo/output"))
628 .withInput(input)
629 .build();
Googler9961a752023-02-23 05:21:53 -0800630 SpawnResult dependentResult = createRemoteSpawnResult(300);
Googler7f326872022-09-20 11:19:43 -0700631
Googler7f326872022-09-20 11:19:43 -0700632 module.spawnExecuted(
Googler17f678b2024-01-31 06:39:16 -0800633 new SpawnExecutedEvent(spawn, new FakeActionInputFileCache(), localResult, Instant.EPOCH));
634 module.spawnExecuted(
635 new SpawnExecutedEvent(
636 spawn, new FakeActionInputFileCache(), remoteResult, Instant.ofEpochMilli(10)));
637 module.spawnExecuted(
638 new SpawnExecutedEvent(
639 dependentSpawn,
640 new FakeActionInputFileCache(),
641 dependentResult,
642 Instant.ofEpochMilli(300)));
Googler7f326872022-09-20 11:19:43 -0700643 module.buildComplete(new BuildCompleteEvent(new BuildResult(1000)));
644
645 ImmutableList<ExecutionGraph.Node> nodes = parse(buffer);
646 assertThat(nodes)
647 .containsExactly(
648 executionGraphNodeBuilderForSpawnBuilderSpawn()
649 .setIndex(0)
Googler034f0242022-09-21 11:34:24 -0700650 .setMetrics(
651 ExecutionGraph.Metrics.newBuilder()
652 .setStartTimestampMillis(0)
653 .setDurationMillis(100)
654 .setOtherMillis(100))
Googler7f326872022-09-20 11:19:43 -0700655 .setRunner("local")
656 .build(),
657 executionGraphNodeBuilderForSpawnBuilderSpawn()
658 .setIndex(1)
Googler034f0242022-09-21 11:34:24 -0700659 .setMetrics(
660 ExecutionGraph.Metrics.newBuilder()
661 .setStartTimestampMillis(10)
662 .setDurationMillis(200)
663 .setOtherMillis(200))
Googler7f326872022-09-20 11:19:43 -0700664 .setRunner("remote")
665 .build(),
666 executionGraphNodeBuilderForSpawnBuilderSpawn()
667 .setIndex(2)
Googler034f0242022-09-21 11:34:24 -0700668 .setMetrics(
669 ExecutionGraph.Metrics.newBuilder()
670 .setStartTimestampMillis(300)
671 .setDurationMillis(300)
672 .setOtherMillis(300))
Googler7f326872022-09-20 11:19:43 -0700673 .setRunner("remote")
674 .addDependentIndex(0)
675 .build())
676 .inOrder();
677 }
678
679 private class FakeOwnerWithPrimaryOutput extends FakeOwner {
680
681 private final String primaryOutput;
682
683 public FakeOwnerWithPrimaryOutput(
684 String mnemonic, String progressMessage, String ownerLabel, String primaryOutput) {
685 super(mnemonic, progressMessage, ownerLabel);
686 this.primaryOutput = primaryOutput;
687 }
688
689 @Override
690 public Artifact getPrimaryOutput() {
691 return ActionsTestUtil.createArtifactWithExecPath(
692 artifactRoot, PathFragment.create(primaryOutput));
693 }
694 }
695
696 private Artifact createOutputArtifact(String rootRelativePath) {
697 return ActionsTestUtil.createArtifactWithExecPath(
698 artifactRoot, artifactRoot.getExecPath().getRelative(rootRelativePath));
699 }
700
Googler9961a752023-02-23 05:21:53 -0800701 private SpawnResult createLocalSpawnResult(int totalTimeInMs) {
Googler7f326872022-09-20 11:19:43 -0700702 return new SpawnResult.Builder()
703 .setRunnerName("local")
704 .setStatus(Status.SUCCESS)
705 .setExitCode(0)
Googler9961a752023-02-23 05:21:53 -0800706 .setSpawnMetrics(
707 SpawnMetrics.Builder.forLocalExec().setTotalTimeInMs(totalTimeInMs).build())
Googler7f326872022-09-20 11:19:43 -0700708 .build();
709 }
710
Googler9961a752023-02-23 05:21:53 -0800711 private SpawnResult createRemoteSpawnResult(int totalTimeInMs) {
Googler7f326872022-09-20 11:19:43 -0700712 return new SpawnResult.Builder()
713 .setRunnerName("remote")
714 .setStatus(Status.SUCCESS)
715 .setExitCode(0)
Googler9961a752023-02-23 05:21:53 -0800716 .setSpawnMetrics(
717 SpawnMetrics.Builder.forRemoteExec().setTotalTimeInMs(totalTimeInMs).build())
Googler7f326872022-09-20 11:19:43 -0700718 .build();
719 }
720
721 /**
722 * Creates a {@link ExecutionGraph.Node.Builder} with pre-populated defaults for spawns created
723 * using {@link SpawnBuilder}.
724 */
725 private ExecutionGraph.Node.Builder executionGraphNodeBuilderForSpawnBuilderSpawn() {
726 return ExecutionGraph.Node.newBuilder()
727 .setDescription("action 'progress message'")
728 .setTargetLabel("//dummy:label")
729 .setMnemonic("Mnemonic")
Googlerc0b09962023-12-04 08:16:44 -0800730 .setRuleClass("dummy-target-kind")
Googler7f326872022-09-20 11:19:43 -0700731 // This comes from SpawnResult.Builder, which defaults to an empty string.
732 .setRunnerSubtype("");
733 }
Googler034f0242022-09-21 11:34:24 -0700734
735 /**
736 * Creates a {@link ExecutionGraph.Node.Builder} with pre-populated defaults for action events.
737 */
738 private ExecutionGraph.Node.Builder executionGraphNodeBuilderForAction(Action action) {
739 return ExecutionGraph.Node.newBuilder()
740 .setDescription(action.prettyPrint())
741 .setTargetLabel(action.getOwner().getLabel().toString())
742 .setMnemonic(action.getMnemonic());
743 }
Googler7f326872022-09-20 11:19:43 -0700744}