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"],