Move `ActionEventRecorder` to open source. PiperOrigin-RevId: 454744480 Change-Id: I17511ec05a9f921ef578782ac37f8eed646a1e0d
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/ActionEventRecorder.java b/src/test/java/com/google/devtools/build/lib/testutil/ActionEventRecorder.java new file mode 100644 index 0000000..66024e9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/testutil/ActionEventRecorder.java
@@ -0,0 +1,360 @@ +// 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.testutil; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.Subscribe; +import com.google.devtools.build.lib.actions.ActionCompletionEvent; +import com.google.devtools.build.lib.actions.ActionExecutedEvent; +import com.google.devtools.build.lib.actions.ActionExecutionMetadata; +import com.google.devtools.build.lib.actions.ActionMiddlemanEvent; +import com.google.devtools.build.lib.actions.ActionResultReceivedEvent; +import com.google.devtools.build.lib.actions.ActionStartedEvent; +import com.google.devtools.build.lib.actions.CachedActionEvent; +import com.google.devtools.build.lib.skyframe.proto.ActionRewind.ActionRewindEvent; +import com.google.devtools.build.lib.skyframe.rewinding.ActionRewindingStats; +import com.google.devtools.build.lib.skyframe.rewinding.ActionRewoundEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** Records various action-related events for tests. */ +public final class ActionEventRecorder { + + private final List<ActionStartedEvent> actionStartedEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<ActionCompletionEvent> actionCompletionEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<ActionExecutedEvent> actionExecutedEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<ActionResultReceivedEvent> actionResultReceivedEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<ActionMiddlemanEvent> actionMiddlemanEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<CachedActionEvent> cachedActionEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<ActionRewoundEvent> actionRewoundEvents = + Collections.synchronizedList(new ArrayList<>()); + private final List<ActionRewindingStats> actionRewindingStatsPosts = + Collections.synchronizedList(new ArrayList<>()); + + private Consumer<ActionRewoundEvent> actionRewoundEventSubscriber = e -> {}; + + public void setActionRewoundEventSubscriber(Consumer<ActionRewoundEvent> subscriber) { + actionRewoundEventSubscriber = subscriber; + } + + public List<ActionStartedEvent> getActionStartedEvents() { + return actionStartedEvents; + } + + public List<ActionCompletionEvent> getActionCompletionEvents() { + return actionCompletionEvents; + } + + public List<ActionExecutedEvent> getActionExecutedEvents() { + return actionExecutedEvents; + } + + public List<ActionResultReceivedEvent> getActionResultReceivedEvents() { + return actionResultReceivedEvents; + } + + public List<ActionRewoundEvent> getActionRewoundEvents() { + return actionRewoundEvents; + } + + public List<ActionRewindingStats> getActionRewindingStatsPosts() { + return actionRewindingStatsPosts; + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionStarted(ActionStartedEvent event) { + actionStartedEvents.add(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionCompleted(ActionCompletionEvent event) { + actionCompletionEvents.add(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionExecuted(ActionExecutedEvent event) { + actionExecutedEvents.add(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionResultReceived(ActionResultReceivedEvent event) { + actionResultReceivedEvents.add(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionMiddleman(ActionMiddlemanEvent event) { + actionMiddlemanEvents.add(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void cachedAction(CachedActionEvent event) { + cachedActionEvents.add(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionRewound(ActionRewoundEvent event) { + actionRewoundEvents.add(event); + actionRewoundEventSubscriber.accept(event); + } + + @SuppressWarnings("unused") + @Subscribe + @AllowConcurrentEvents + void actionRewindingStats(ActionRewindingStats actionRewindingStats) { + actionRewindingStatsPosts.add(actionRewindingStats); + } + + public void clear() { + actionStartedEvents.clear(); + actionCompletionEvents.clear(); + actionExecutedEvents.clear(); + actionResultReceivedEvents.clear(); + actionMiddlemanEvents.clear(); + cachedActionEvents.clear(); + actionRewoundEvents.clear(); + actionRewindingStatsPosts.clear(); + } + + /** + * Check how many of each type of event was emitted during a successful build. + * + * @param runOnce Actions which ran and are not rewound + * @param completedRewound Actions which ran and then are rewound by a later failed action + * @param failedRewound Actions which fail because of lost inputs and which rewind themselves and + * the actions that generate those lost inputs + * @param exactlyOneMiddlemanEventChecks A list of predicates which should be satisfied exactly + * once by the sequence of middleman events emitted + */ + public void assertEvents( + ImmutableList<String> runOnce, + ImmutableList<String> completedRewound, + ImmutableList<String> failedRewound, + ImmutableList<Predicate<ActionMiddlemanEvent>> exactlyOneMiddlemanEventChecks, + ImmutableList<Integer> actionRewindingPostLostInputCounts) { + assertEvents( + runOnce, + completedRewound, + failedRewound, + exactlyOneMiddlemanEventChecks, + /*expectResultReceivedForFailedRewound=*/ true, + actionRewindingPostLostInputCounts, + /*lostInputAndActionsPosts=*/ ImmutableList.of()); + } + + /** + * Like {@link #assertEvents(ImmutableList, ImmutableList, ImmutableList, ImmutableList, + * ImmutableList)}. The {@code expectResultReceivedForFailedRewound} should be true iff the failed + * rewound actions ever successfully complete. + * + * @param expectResultReceivedForFailedRewound whether the failed rewound actions ever + * successfully complete, because no {@link ActionResultReceivedEvent} is emitted for a failed + * action + */ + public void assertEvents( + ImmutableList<String> runOnce, + ImmutableList<String> completedRewound, + ImmutableList<String> failedRewound, + ImmutableList<Predicate<ActionMiddlemanEvent>> exactlyOneMiddlemanEventChecks, + boolean expectResultReceivedForFailedRewound, + ImmutableList<Integer> actionRewindingPostLostInputCounts, + ImmutableList<ImmutableList<ActionRewindEventAsserter>> lostInputAndActionsPosts) { + EventCountAsserter eventCountAsserter = + new EventCountAsserter(runOnce, completedRewound, failedRewound); + + eventCountAsserter.assertEventCounts( + /*events=*/ actionStartedEvents, + /*eventsName=*/ "actionStartedEvents", + /*converter=*/ e -> progressMessageOrPrettyPrint(e.getAction()), + /*expectedRunOnceEventCount=*/ 1, + /*expectedCompletedRewoundEventCount=*/ 1, + /*expectedFailedRewoundEventCount=*/ 2); + + eventCountAsserter.assertEventCounts( + /*events=*/ actionCompletionEvents, + /*eventsName=*/ "actionCompletionEvents", + /*converter=*/ e -> progressMessageOrPrettyPrint(e.getAction()), + /*expectedRunOnceEventCount=*/ 1, + /*expectedCompletedRewoundEventCount=*/ 1, + /*expectedFailedRewoundEventCount=*/ 1); + + eventCountAsserter.assertEventCounts( + /*events=*/ actionExecutedEvents, + /*eventsName=*/ "actionExecutedEvents", + /*converter=*/ e -> progressMessageOrPrettyPrint(e.getAction()), + /*expectedRunOnceEventCount=*/ 1, + /*expectedCompletedRewoundEventCount=*/ 1, + /*expectedFailedRewoundEventCount=*/ 1); + + eventCountAsserter.assertEventCounts( + /*events=*/ actionResultReceivedEvents, + /*eventsName=*/ "actionResultReceivedEvents", + /*converter=*/ e -> progressMessageOrPrettyPrint(e.getAction()), + /*expectedRunOnceEventCount=*/ 1, + /*expectedCompletedRewoundEventCount=*/ 1, + /*expectedFailedRewoundEventCount=*/ expectResultReceivedForFailedRewound ? 1 : 0); + + eventCountAsserter.assertEventCounts( + /*events=*/ actionRewoundEvents, + /*eventsName=*/ "actionRewoundEvents", + /*converter=*/ e -> progressMessageOrPrettyPrint(e.getFailedRewoundAction()), + /*expectedRunOnceEventCount=*/ 0, + /*expectedCompletedRewoundEventCount=*/ 0, + /*expectedFailedRewoundEventCount=*/ 1); + + assertRewindActionStats(actionRewindingPostLostInputCounts, lostInputAndActionsPosts); + + for (Predicate<ActionMiddlemanEvent> check : exactlyOneMiddlemanEventChecks) { + assertThat(actionMiddlemanEvents.stream().filter(check).count()).isEqualTo(1); + } + assertThat(cachedActionEvents).isEmpty(); + } + + /** + * Assert that the contents of the {@link ActionRewindingStats} event matches expected results. + * + * @param totalLostInputCounts - The list of the counts of all lost inputs logged with each + * ActionRewindingStats post. + * @param lostInputAndActionsPosts - The list of all expected values in all ActionRewindingStats + * posts. The outer list represents the ActionRewindingStats posts, the inner list contains + * {@link ActionRewindEventAsserter} that has the expected values of each action rewind event + * within each corresponding post. + */ + public void assertRewindActionStats( + ImmutableList<Integer> totalLostInputCounts, + ImmutableList<ImmutableList<ActionRewindEventAsserter>> lostInputAndActionsPosts) { + assertThat(actionRewindingStatsPosts).hasSize(totalLostInputCounts.size()); + for (int postIndex = 0; postIndex < lostInputAndActionsPosts.size(); postIndex++) { + ActionRewindingStats actionRewindingStats = actionRewindingStatsPosts.get(postIndex); + assertThat(actionRewindingStats.lostInputsCount()) + .isEqualTo(totalLostInputCounts.get(postIndex)); + ImmutableList<ActionRewindEventAsserter> expectedLostInputPost = + lostInputAndActionsPosts.get(postIndex); + + for (int r = 0; r < actionRewindingStats.actionRewindEvents().size(); r++) { + ActionRewindEvent actualActionRewind = actionRewindingStats.actionRewindEvents().get(r); + expectedLostInputPost.get(r).assertActionRewindEvent(actualActionRewind); + } + } + } + + @AutoValue + public abstract static class ActionRewindEventAsserter { + + public static ActionRewindEventAsserter create( + String expectedActionMnemonic, + String expectedActionOwnerLabel, + int expectedLostInputsCount, + int expectedInvalidatedNodesCount) { + return new AutoValue_ActionEventRecorder_ActionRewindEventAsserter( + expectedActionMnemonic, + expectedActionOwnerLabel, + expectedLostInputsCount, + expectedInvalidatedNodesCount); + } + + ActionRewindEventAsserter() {} + + private void assertActionRewindEvent(ActionRewindEvent actualEvent) { + assertThat(actualEvent.getActionDescription().getType()).isEqualTo(expectedActionMnemonic()); + assertThat(actualEvent.getActionDescription().getRuleLabel()) + .isEqualTo(expectedActionOwnerLabel()); + assertThat(actualEvent.getTotalLostInputsCount()).isEqualTo(expectedLostInputsCount()); + assertThat(actualEvent.getInvalidatedNodesCount()).isEqualTo(expectedInvalidatedNodesCount()); + } + + abstract String expectedActionMnemonic(); + + abstract String expectedActionOwnerLabel(); + + abstract int expectedLostInputsCount(); + + abstract int expectedInvalidatedNodesCount(); + } + + private static final class EventCountAsserter { + private final ImmutableList<String> runOnce; + private final ImmutableList<String> completedRewound; + private final ImmutableList<String> failedRewound; + + private EventCountAsserter( + ImmutableList<String> runOnce, + ImmutableList<String> completedRewound, + ImmutableList<String> failedRewound) { + this.runOnce = runOnce; + this.completedRewound = completedRewound; + this.failedRewound = failedRewound; + } + + private <T> void assertEventCounts( + List<T> events, + String eventsName, + Function<T, String> converter, + int expectedRunOnceEventCount, + int expectedCompletedRewoundEventCount, + int expectedFailedRewoundEventCount) { + ImmutableList<String> eventDescriptions = + events.stream().map(converter).collect(toImmutableList()); + for (String runOnceAction : runOnce) { + assertWithMessage("Run-once action \"%s\" in %s", runOnceAction, eventsName) + .that(eventDescriptions.stream().filter(d -> d.equals(runOnceAction)).count()) + .isEqualTo(expectedRunOnceEventCount); + } + for (String rewoundAction : completedRewound) { + assertWithMessage("Completed rewound action \"%s\" in %s", rewoundAction, eventsName) + .that(eventDescriptions.stream().filter(d -> d.equals(rewoundAction)).count()) + .isEqualTo(expectedCompletedRewoundEventCount); + } + for (String failedRewoundAction : failedRewound) { + assertWithMessage("Failed rewound action \"%s\" in %s", failedRewoundAction, eventsName) + .that(eventDescriptions.stream().filter(d -> d.equals(failedRewoundAction)).count()) + .isEqualTo(expectedFailedRewoundEventCount); + } + } + } + + public static String progressMessageOrPrettyPrint(ActionExecutionMetadata action) { + String progressMessage = action.getProgressMessage(); + return progressMessage != null ? progressMessage : action.prettyPrint(); + } +}
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/BUILD b/src/test/java/com/google/devtools/build/lib/testutil/BUILD index 031ea9b..80f418c 100644 --- a/src/test/java/com/google/devtools/build/lib/testutil/BUILD +++ b/src/test/java/com/google/devtools/build/lib/testutil/BUILD
@@ -80,6 +80,20 @@ ) java_library( + name = "action_event_recorder", + srcs = ["ActionEventRecorder.java"], + deps = [ + "//src/main/java/com/google/devtools/build/lib/actions", + "//src/main/java/com/google/devtools/build/lib/skyframe/proto:action_rewind_event_java_proto", + "//src/main/java/com/google/devtools/build/lib/skyframe/rewinding", + "//src/main/java/com/google/devtools/build/lib/skyframe/rewinding:action_rewound_event", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:truth", + ], +) + +java_library( name = "BazelPackageBuilderHelperForTesting", testonly = 0, srcs = ["BazelPackageLoadingListenerForTesting.java"],