blob: 66024e97a149d75920361bcb1d046dacf2c8f93b [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.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();
}
}