// Copyright 2016 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.EventReportingArtifacts;
import com.google.devtools.build.lib.actions.Root;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildEventWithConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.buildeventstream.AnnounceBuildEventTransportsEvent;
import com.google.devtools.build.lib.buildeventstream.ArtifactGroupNamer;
import com.google.devtools.build.lib.buildeventstream.BuildEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventConverters;
import com.google.devtools.build.lib.buildeventstream.BuildEventId;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.NamedSetOfFilesId;
import com.google.devtools.build.lib.buildeventstream.BuildEventTransport;
import com.google.devtools.build.lib.buildeventstream.BuildEventTransportClosedEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventWithOrderConstraint;
import com.google.devtools.build.lib.buildeventstream.GenericBuildEvent;
import com.google.devtools.build.lib.buildeventstream.PathConverter;
import com.google.devtools.build.lib.buildeventstream.ProgressEvent;
import com.google.devtools.build.lib.buildtool.BuildResult;
import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.NestedSetView;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/** Tests {@link BuildEventStreamer}. */
@RunWith(JUnit4.class)
public class BuildEventStreamerTest extends FoundationTestCase {
private static class RecordingBuildEventTransport implements BuildEventTransport {
private final List<BuildEvent> events = new ArrayList<>();
private final List<BuildEventStreamProtos.BuildEvent> eventsAsProtos = new ArrayList<>();
@Override
public String name() {
return this.getClass().getSimpleName();
}
@Override
public void sendBuildEvent(BuildEvent event, final ArtifactGroupNamer namer) {
events.add(event);
eventsAsProtos.add(
event.asStreamProto(
new BuildEventConverters() {
@Override
public ArtifactGroupNamer artifactGroupNamer() {
return namer;
}
@Override
public PathConverter pathConverter() {
return new PathConverter() {
@Override
public String apply(Path path) {
return path.toString();
}
};
}
}));
}
@Override
public ListenableFuture<Void> close() {
return Futures.immediateFuture(null);
}
List<BuildEvent> getEvents() {
return events;
}
List<BuildEventStreamProtos.BuildEvent> getEventProtos() {
return eventsAsProtos;
}
}
private static class GenericOrderEvent implements BuildEventWithOrderConstraint {
private final BuildEventId id;
private final Collection<BuildEventId> children;
private final Collection<BuildEventId> after;
GenericOrderEvent(
BuildEventId id, Collection<BuildEventId> children, Collection<BuildEventId> after) {
this.id = id;
this.children = children;
this.after = after;
}
GenericOrderEvent(BuildEventId id, Collection<BuildEventId> children) {
this(id, children, children);
}
@Override
public BuildEventId getEventId() {
return id;
}
@Override
public Collection<BuildEventId> getChildrenEvents() {
return children;
}
@Override
public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventConverters converters) {
return GenericBuildEvent.protoChaining(this).build();
}
@Override
public Collection<BuildEventId> postedAfter() {
return after;
}
}
private static class GenericArtifactReportingEvent implements EventReportingArtifacts {
private final BuildEventId id;
private final Collection<BuildEventId> children;
private final Collection<NestedSet<Artifact>> artifacts;
GenericArtifactReportingEvent(
BuildEventId id,
Collection<BuildEventId> children,
Collection<NestedSet<Artifact>> artifacts) {
this.id = id;
this.children = children;
this.artifacts = artifacts;
}
GenericArtifactReportingEvent(BuildEventId id, Collection<NestedSet<Artifact>> artifacts) {
this(id, ImmutableSet.<BuildEventId>of(), artifacts);
}
@Override
public BuildEventId getEventId() {
return id;
}
@Override
public Collection<BuildEventId> getChildrenEvents() {
return children;
}
@Override
public Collection<NestedSet<Artifact>> reportedArtifacts() {
return artifacts;
}
@Override
public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventConverters converters) {
BuildEventStreamProtos.NamedSetOfFiles.Builder builder =
BuildEventStreamProtos.NamedSetOfFiles.newBuilder();
for (NestedSet<Artifact> artifactset : artifacts) {
builder.addFileSets(
converters
.artifactGroupNamer()
.apply((new NestedSetView<Artifact>(artifactset)).identifier()));
}
return GenericBuildEvent.protoChaining(this).setNamedSetOfFiles(builder.build()).build();
}
}
private static class GenericConfigurationEvent implements BuildEventWithConfiguration {
private final BuildEventId id;
private final Collection<BuildEventId> children;
private final Collection<BuildConfiguration> configurations;
GenericConfigurationEvent(
BuildEventId id,
Collection<BuildEventId> children,
Collection<BuildConfiguration> configurations) {
this.id = id;
this.children = children;
this.configurations = configurations;
}
GenericConfigurationEvent(BuildEventId id, BuildConfiguration configuration) {
this(id, ImmutableSet.<BuildEventId>of(), ImmutableSet.of(configuration));
}
@Override
public BuildEventId getEventId() {
return id;
}
@Override
public Collection<BuildEventId> getChildrenEvents() {
return children;
}
@Override
public Collection<BuildConfiguration> getConfigurations() {
return configurations;
}
@Override
public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventConverters converters) {
return GenericBuildEvent.protoChaining(this).build();
}
}
private static BuildEventId testId(String opaque) {
return BuildEventId.unknownBuildEventId(opaque);
}
private static class EventBusHandler {
Set<BuildEventTransport> transportSet;
@Subscribe
void transportsAnnounced(AnnounceBuildEventTransportsEvent evt) {
transportSet = Collections.synchronizedSet(new HashSet<>(evt.transports()));
}
@Subscribe
void transportClosed(BuildEventTransportClosedEvent evt) {
transportSet.remove(evt.transport());
}
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test(timeout = 5000)
public void testSimpleStream() {
// Verify that a well-formed event is passed through and that completion of the
// build clears the pending progress-update event.
EventBusHandler handler = new EventBusHandler();
eventBus.register(handler);
assertNull(handler.transportSet);
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"), ImmutableSet.of(ProgressEvent.INITIAL_PROGRESS_UPDATE,
BuildEventId.buildFinished()));
streamer.buildEvent(startEvent);
List<BuildEvent> afterFirstEvent = transport.getEvents();
assertThat(afterFirstEvent).hasSize(1);
assertEquals(startEvent.getEventId(), afterFirstEvent.get(0).getEventId());
assertEquals(1, handler.transportSet.size());
streamer.buildEvent(new BuildCompleteEvent(new BuildResult(0)));
List<BuildEvent> finalStream = transport.getEvents();
assertThat(finalStream).hasSize(3);
assertEquals(BuildEventId.buildFinished(), finalStream.get(1).getEventId());
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, finalStream.get(2).getEventId());
while (!handler.transportSet.isEmpty()) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
}
}
@Test
public void testChaining() {
// Verify that unannounced events are linked in with progress update events, assuming
// a correctly formed initial event.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"), ImmutableSet.of(ProgressEvent.INITIAL_PROGRESS_UPDATE));
BuildEvent unexpectedEvent =
new GenericBuildEvent(testId("unexpected"), ImmutableSet.<BuildEventId>of());
streamer.buildEvent(startEvent);
streamer.buildEvent(unexpectedEvent);
List<BuildEvent> eventsSeen = transport.getEvents();
assertThat(eventsSeen).hasSize(3);
assertEquals(startEvent.getEventId(), eventsSeen.get(0).getEventId());
assertEquals(unexpectedEvent.getEventId(), eventsSeen.get(2).getEventId());
BuildEvent linkEvent = eventsSeen.get(1);
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, linkEvent.getEventId());
assertTrue(
"Unexpected events should be linked",
linkEvent.getChildrenEvents().contains(unexpectedEvent.getEventId()));
}
@Test
public void testBadInitialEvent() {
// Verify that, if the initial event does not announce the initial progress update event,
// the initial progress event is used instead to chain that event; in this way, new
// progress updates can always be chained in.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEvent unexpectedStartEvent =
new GenericBuildEvent(testId("unexpected start"), ImmutableSet.<BuildEventId>of());
streamer.buildEvent(unexpectedStartEvent);
List<BuildEvent> eventsSeen = transport.getEvents();
assertThat(eventsSeen).hasSize(2);
assertEquals(unexpectedStartEvent.getEventId(), eventsSeen.get(1).getEventId());
BuildEvent initial = eventsSeen.get(0);
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, initial.getEventId());
assertTrue(
"Event should be linked",
initial.getChildrenEvents().contains(unexpectedStartEvent.getEventId()));
// The initial event should also announce a new progress event; we test this
// by streaming another unannounced event.
BuildEvent unexpectedEvent =
new GenericBuildEvent(testId("unexpected"), ImmutableSet.<BuildEventId>of());
streamer.buildEvent(unexpectedEvent);
List<BuildEvent> allEventsSeen = transport.getEvents();
assertThat(allEventsSeen).hasSize(4);
assertEquals(unexpectedEvent.getEventId(), allEventsSeen.get(3).getEventId());
BuildEvent secondLinkEvent = allEventsSeen.get(2);
assertTrue(
"Progress should have been announced",
initial.getChildrenEvents().contains(secondLinkEvent.getEventId()));
assertTrue(
"Second event should be linked",
secondLinkEvent.getChildrenEvents().contains(unexpectedEvent.getEventId()));
}
@Test
public void testReferPastEvent() {
// Verify that, if an event is refers to a previously done event, that duplicated
// late-referenced event is not expected again.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"),
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE,
BuildEventId.buildFinished()));
BuildEvent earlyEvent =
new GenericBuildEvent(testId("unexpected"), ImmutableSet.<BuildEventId>of());
BuildEvent lateReference =
new GenericBuildEvent(testId("late reference"), ImmutableSet.of(earlyEvent.getEventId()));
streamer.buildEvent(startEvent);
streamer.buildEvent(earlyEvent);
streamer.buildEvent(lateReference);
streamer.buildEvent(new BuildCompleteEvent(new BuildResult(0)));
List<BuildEvent> eventsSeen = transport.getEvents();
int earlyEventCount = 0;
for (BuildEvent event : eventsSeen) {
if (event.getEventId().equals(earlyEvent.getEventId())) {
earlyEventCount++;
}
}
// The early event should be reported precisely once.
assertEquals(1, earlyEventCount);
}
@Test
public void testReodering() {
// Verify that an event requiring to be posted after another one is indeed.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEventId expectedId = testId("the target");
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"),
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE, expectedId));
BuildEvent rootCause =
new GenericBuildEvent(testId("failure event"), ImmutableSet.<BuildEventId>of());
BuildEvent failedTarget =
new GenericOrderEvent(expectedId, ImmutableSet.<BuildEventId>of(rootCause.getEventId()));
streamer.buildEvent(startEvent);
streamer.buildEvent(failedTarget);
streamer.buildEvent(rootCause);
List<BuildEvent> allEventsSeen = transport.getEvents();
assertThat(allEventsSeen).hasSize(4);
assertEquals(startEvent.getEventId(), allEventsSeen.get(0).getEventId());
BuildEvent linkEvent = allEventsSeen.get(1);
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, linkEvent.getEventId());
assertEquals(rootCause.getEventId(), allEventsSeen.get(2).getEventId());
assertEquals(failedTarget.getEventId(), allEventsSeen.get(3).getEventId());
}
@Test
public void testMissingPrerequisits() {
// Verify that an event where the prerequisite is never coming till the end of
// the build still gets posted, with the prerequisite aborted.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEventId expectedId = testId("the target");
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"),
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE, expectedId,
BuildEventId.buildFinished()));
BuildEventId rootCauseId = testId("failure event");
BuildEvent failedTarget =
new GenericOrderEvent(expectedId, ImmutableSet.<BuildEventId>of(rootCauseId));
streamer.buildEvent(startEvent);
streamer.buildEvent(failedTarget);
streamer.buildEvent(new BuildCompleteEvent(new BuildResult(0)));
List<BuildEvent> allEventsSeen = transport.getEvents();
assertThat(allEventsSeen).hasSize(6);
assertEquals(startEvent.getEventId(), allEventsSeen.get(0).getEventId());
assertEquals(BuildEventId.buildFinished(), allEventsSeen.get(1).getEventId());
BuildEvent linkEvent = allEventsSeen.get(2);
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, linkEvent.getEventId());
assertEquals(rootCauseId, allEventsSeen.get(3).getEventId());
assertEquals(failedTarget.getEventId(), allEventsSeen.get(4).getEventId());
}
@Test
public void testVeryFirstEventNeedsToWait() {
// Verify that we can handle an first event waiting for another event.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEventId initialId = testId("Initial");
BuildEventId waitId = testId("Waiting for initial event");
BuildEvent startEvent =
new GenericBuildEvent(
initialId,
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE, waitId));
BuildEvent waitingForStart =
new GenericOrderEvent(waitId, ImmutableSet.<BuildEventId>of(), ImmutableSet.of(initialId));
streamer.buildEvent(waitingForStart);
streamer.buildEvent(startEvent);
List<BuildEvent> allEventsSeen = transport.getEvents();
assertThat(allEventsSeen).hasSize(2);
assertEquals(startEvent.getEventId(), allEventsSeen.get(0).getEventId());
assertEquals(waitingForStart.getEventId(), allEventsSeen.get(1).getEventId());
}
private Artifact makeArtifact(String pathString) {
Path path = outputBase.getRelative(PathFragment.create(pathString));
return new Artifact(path, Root.asSourceRoot(path));
}
@Test
public void testReportedArtifacts() {
// Verify that reported artifacts are correctly unfolded into the stream
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"),
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE));
Artifact a = makeArtifact("path/a");
Artifact b = makeArtifact("path/b");
Artifact c = makeArtifact("path/c");
NestedSet<Artifact> innerGroup = NestedSetBuilder.<Artifact>stableOrder().add(a).add(b).build();
NestedSet<Artifact> group =
NestedSetBuilder.<Artifact>stableOrder().addTransitive(innerGroup).add(c).build();
BuildEvent reportingArtifacts =
new GenericArtifactReportingEvent(testId("reporting"), ImmutableSet.of(group));
streamer.buildEvent(startEvent);
streamer.buildEvent(reportingArtifacts);
List<BuildEvent> allEventsSeen = transport.getEvents();
List<BuildEventStreamProtos.BuildEvent> eventProtos = transport.getEventProtos();
assertEquals(7, allEventsSeen.size());
assertEquals(startEvent.getEventId(), allEventsSeen.get(0).getEventId());
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, allEventsSeen.get(1).getEventId());
List<BuildEventStreamProtos.File> firstSetDirects =
eventProtos.get(2).getNamedSetOfFiles().getFilesList();
assertEquals(2, firstSetDirects.size());
assertEquals(
ImmutableSet.of(a.getPath().toString(), b.getPath().toString()),
ImmutableSet.of(firstSetDirects.get(0).getUri(), firstSetDirects.get(1).getUri()));
List<NamedSetOfFilesId> secondSetTransitives =
eventProtos.get(4).getNamedSetOfFiles().getFileSetsList();
assertEquals(1, secondSetTransitives.size());
assertEquals(eventProtos.get(2).getId().getNamedSet(), secondSetTransitives.get(0));
List<NamedSetOfFilesId> reportedArtifactSets =
eventProtos.get(6).getNamedSetOfFiles().getFileSetsList();
assertEquals(1, reportedArtifactSets.size());
assertEquals(eventProtos.get(4).getId().getNamedSet(), reportedArtifactSets.get(0));
}
@Test
public void testStdoutReported() {
// Verify that stdout and stderr are reported in the build-event stream on progress
// events.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEventStreamer.OutErrProvider outErr =
Mockito.mock(BuildEventStreamer.OutErrProvider.class);
String stdoutMsg = "Some text that was written to stdout.";
String stderrMsg = "The UI text that bazel wrote to stderr.";
when(outErr.getOut()).thenReturn(stdoutMsg);
when(outErr.getErr()).thenReturn(stderrMsg);
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"),
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE));
BuildEvent unexpectedEvent =
new GenericBuildEvent(testId("unexpected"), ImmutableSet.<BuildEventId>of());
streamer.registerOutErrProvider(outErr);
streamer.buildEvent(startEvent);
streamer.buildEvent(unexpectedEvent);
List<BuildEvent> eventsSeen = transport.getEvents();
assertThat(eventsSeen).hasSize(3);
assertEquals(startEvent.getEventId(), eventsSeen.get(0).getEventId());
assertEquals(unexpectedEvent.getEventId(), eventsSeen.get(2).getEventId());
BuildEvent linkEvent = eventsSeen.get(1);
BuildEventStreamProtos.BuildEvent linkEventProto = transport.getEventProtos().get(1);
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, linkEvent.getEventId());
assertTrue(
"Unexpected events should be linked",
linkEvent.getChildrenEvents().contains(unexpectedEvent.getEventId()));
assertEquals(stdoutMsg, linkEventProto.getProgress().getStdout());
assertEquals(stderrMsg, linkEventProto.getProgress().getStderr());
// As there is only one progress event, the OutErrProvider should be queried
// only once for stdout and stderr.
verify(outErr, times(1)).getOut();
verify(outErr, times(1)).getErr();
}
@Test
public void testReportedConfigurations() throws Exception {
// Verify that configuration events are posted, but only once.
RecordingBuildEventTransport transport = new RecordingBuildEventTransport();
BuildEventStreamer streamer =
new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter);
BuildEvent startEvent =
new GenericBuildEvent(
testId("Initial"),
ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE));
BuildConfiguration configuration =
new BuildConfiguration(
new BlazeDirectories(outputBase, outputBase, rootDirectory, "productName"),
ImmutableMap.<Class<? extends BuildConfiguration.Fragment>,
BuildConfiguration.Fragment>of(),
BuildOptions.of(ImmutableList.<Class<? extends FragmentOptions>>of(
BuildConfiguration.Options.class)));
BuildEvent firstWithConfiguration =
new GenericConfigurationEvent(testId("first"), configuration);
BuildEvent secondWithConfiguration =
new GenericConfigurationEvent(testId("second"), configuration);
streamer.buildEvent(startEvent);
streamer.buildEvent(firstWithConfiguration);
streamer.buildEvent(secondWithConfiguration);
List<BuildEvent> allEventsSeen = transport.getEvents();
assertEquals(7, allEventsSeen.size());
assertEquals(startEvent.getEventId(), allEventsSeen.get(0).getEventId());
assertEquals(ProgressEvent.INITIAL_PROGRESS_UPDATE, allEventsSeen.get(1).getEventId());
assertEquals(configuration, allEventsSeen.get(2));
assertEquals(BuildEventId.progressId(1), allEventsSeen.get(3).getEventId());
assertEquals(firstWithConfiguration, allEventsSeen.get(4));
assertEquals(BuildEventId.progressId(2), allEventsSeen.get(5).getEventId());
assertEquals(secondWithConfiguration, allEventsSeen.get(6));
}
}