Make it possible to re-create ArtifactCreated events This change add a force flag, which when is true make it possible to create ArtifactCreated event for reused tags. This flag can only be used when creating events through the REST endpoints. The creation of ArtifactCreated event is also changed sligthly. Before we always checked if there already was a CD and if there was we used it. Now we skip this check and always create a new CD for each new ArtifactCreated event. It should not matter much because if we create a new ArtifactCreated event there should almost never be a CD we can reuse(an exception is if Gerrit crash directly after a CD creation). Solves: Jira GER-1573 Change-Id: I6622fa714a99aaa1a121cedfdf3b567d435ebc4f
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java index 0dd782e..8d6cab9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHub.java
@@ -50,7 +50,7 @@ * * <p>{@code EventKey.fromEvent(this).equals(EventKey.fromEvent(existing)} * - * <p>is true + * <p>is true and force is set to false * * <p><b>NOTE:</b> * @@ -58,9 +58,10 @@ * used after adding the event to this hub. * * @param event + * @param force Create the event even if there already exists an event with an identical key. * @throws InterruptedException */ - public void push(EiffelEvent event) throws InterruptedException; + public void push(EiffelEvent event, boolean force) throws InterruptedException; /** * If an event exists, already published or awaiting publish in this EiffelEventHub, such that: *
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java index 37e96b6..259b080 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubImpl.java
@@ -64,11 +64,14 @@ } @Override - public void push(EiffelEvent event) throws InterruptedException { + public void push(EiffelEvent event, boolean force) throws InterruptedException { checkState(event != null, "Event must not be null."); checkState(EventKey.isSupported(event), "Unsupported type: " + event.meta.type.name()); EventKey key = EventKey.fromEvent(event); + checkState( + !force || key.supportsForce(), "This event does not support force: " + key.toString()); + final ReentrantLock putLock = this.putLock; final ReentrantLock idLookupLock = this.idLookupLock; final AtomicInteger count = this.count; @@ -88,7 +91,7 @@ } idLookupLock.lock(); try { - if (getExistingId(key, true).isPresent()) { + if (!force && getExistingId(key, true).isPresent()) { logger.atWarning().log("Event %s is already pushed.", key); return; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/EventKey.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/EventKey.java index 21c848e..fe582d4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/EventKey.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/EventKey.java
@@ -64,6 +64,16 @@ return this.equals(fromEvent(event)); } + public boolean supportsForce() { + switch (type) { + case ARTC: + case CD: + return true; + default: + return false; + } + } + @Override public String toString() { return String.format("%s(%s)", type.name(), type.getType());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventFactory.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventFactory.java index 192d6c3..07522fd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventFactory.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventFactory.java
@@ -275,7 +275,7 @@ return URLEncoder.encode(name, Charsets.UTF_8); } - private String tagPURL(String projectName, String tagName) { + public String tagPURL(String projectName, String tagName) { return String.format(tagPURLTemplate, projectName, tagName, projectUrl(projectName), tagName); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventMapper.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventMapper.java index 744628d..10c4fa1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventMapper.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/mapping/EiffelEventMapper.java
@@ -177,6 +177,10 @@ return eventFactoryProvider.get().tagCompositionName(projectName); } + public String tagPURL(String projectName, String tagName) { + return eventFactoryProvider.get().tagPURL(projectName, tagName); + } + private boolean isCurrentPatchsetOf(RevCommit commit, ChangeData change) { return change .notes()
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java index 5b8677d..771816c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParser.java
@@ -213,18 +213,28 @@ } } - public void createAndScheduleArtc(String projectName, String tagName, Long creationTime) { - CompositionDefinedEventKey cd = - CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName); + public void createAndScheduleArtc( + String projectName, String tagName, Long creationTime, boolean force) { try { - Optional<UUID> cdId = eventHub.getExistingId(cd); - if (cdId.isEmpty()) { - /* Cd event is missing, create it first */ - createAndScheduleCd(projectName, tagName, creationTime); - cdId = eventHub.getExistingId(cd); - } - if (!cdId.isEmpty()) { - pushToHub(mapper.toArtc(projectName, tagName, creationTime, cdId.get())); + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName); + Optional<UUID> oldCdId = eventHub.getExistingId(cd); + if (oldCdId.isEmpty() || force) { + createAndScheduleCd(projectName, tagName, creationTime, force); + Optional<UUID> cdId = eventHub.getExistingId(cd); + if (cdId.isPresent() && !cdId.equals(oldCdId)) { + pushToHub(mapper.toArtc(projectName, tagName, creationTime, cdId.get()), force); + if (oldCdId.isPresent()) { + logger.atInfo().log( + "Event Artc has been forcibly created for: %s, %s", projectName, tagName); + } else { + logger.atFine().log("Event Artc has been created for: %s, %s", projectName, tagName); + } + } + } else { + /* Artc event has already been created */ + logger.atWarning().log( + "Event Artc has already been created for: %s, %s", projectName, tagName); } } catch (EiffelEventIdLookupException | InterruptedException e) { logger.atSevere().withCause(e).log( @@ -232,7 +242,8 @@ } } - private void createAndScheduleCd(String projectName, String tagName, Long creationTime) { + private void createAndScheduleCd( + String projectName, String tagName, Long creationTime, boolean force) { SourceChangeEventKey scs = null; Optional<UUID> scsId = Optional.empty(); @@ -263,7 +274,7 @@ logger.atSevere().log("Could not find SCS for: %s in %s", commitId, projectName); return; } - pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get())); + pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get()), force); } catch (EiffelEventIdLookupException | InterruptedException e) { logger.atSevere().withCause(e).log( "Event creation failed for: %s", @@ -301,11 +312,15 @@ } private void pushToHub(EiffelEvent toPush) throws InterruptedException { + pushToHub(toPush, false); + } + + private void pushToHub(EiffelEvent toPush, boolean force) throws InterruptedException { int failureCount = 0; EventKey key = EventKey.fromEvent(toPush); while (true) { try { - eventHub.push(toPush); + eventHub.push(toPush, force); logger.atFine().log("Successfully pushed %s to EventHub", key); return; } catch (InterruptedException e) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java index 7d57e58..1fd7ec5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueue.java
@@ -146,27 +146,30 @@ scheduleArtcCreation( event.getProjectName(), event.getRefName().substring(RefNames.REFS_TAGS.length()), - TimeUtil.nowMs()); + TimeUtil.nowMs(), + false); } - public void scheduleArtcCreation(TagResource resource) { + public void scheduleArtcCreation(TagResource resource, boolean force) { String tagRef = resource.getRef(); scheduleArtcCreation( resource.getName(), tagRef.startsWith(RefNames.REFS_TAGS) ? tagRef.substring(RefNames.REFS_TAGS.length()) : tagRef, - resource.getTagInfo().created.getTime()); + resource.getTagInfo().created.getTime(), + force); } - public void scheduleArtcCreation(String projectName, String tagName, Long creationTime) { + public void scheduleArtcCreation( + String projectName, String tagName, Long creationTime, boolean force) { schedule( new EventParsingWorker(ARTC, projectName, tagName) { @Override public void doRun() { try { - eventParser.createAndScheduleArtc(projectName, tagName, creationTime); + eventParser.createAndScheduleArtc(projectName, tagName, creationTime, force); } catch (Exception e) { logger.atSevere().withCause(e).log( "Failed to create ARTC for %s:%s", projectName, tagName);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateArtcs.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateArtcs.java index 0bc0244..9a07c0f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateArtcs.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateArtcs.java
@@ -29,7 +29,9 @@ @Singleton @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) public class CreateArtcs implements RestModifyView<TagResource, CreateArtcs.Input> { - static class Input {} + static class Input { + boolean force; + } private final EiffelEventParsingQueue queue; @@ -41,7 +43,7 @@ @Override public Response<?> apply(TagResource resource, CreateArtcs.Input input) throws AuthException, BadRequestException, ResourceConflictException, Exception { - queue.scheduleArtcCreation(resource); + queue.scheduleArtcCreation(resource, input.force); return EventCreationResponse.artc(resource); } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubIT.java index 3cd4ff0..97febc0 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventHubIT.java
@@ -60,7 +60,7 @@ @Test public void eventIsPassedThroughHub() throws Exception { EiffelEvent toPublish = TestUtil.newScc(); - hub.push(toPublish); + hub.push(toPublish, false); EiffelEvent published = publisher.getPublished(EventKey.fromEvent(toPublish)); assertNotNull(published); assertEquals(toPublish.meta.id, published.meta.id); @@ -72,7 +72,7 @@ EventKey key = EventKey.fromEvent(toCheck); assertFalse(hub.getExistingId(key).isPresent()); publisher.stopPublishing(); - hub.push(toCheck); + hub.push(toCheck, false); assertTrue(hub.getExistingId(key).isPresent()); publisher.startPublishing(); assertTrue(hub.getExistingId(key).isPresent()); @@ -88,7 +88,7 @@ assertFalse(hub.getScsForCommit(repo, commit, List.of()).isPresent()); assertEquals(1, TestEventStorage.INSTANCE.queriesForScsIds(repo, commit)); publisher.stopPublishing(); - hub.push(toCheck); + hub.push(toCheck, false); assertFalse(hub.getScsForCommit(repo, commit, List.of()).isPresent()); assertEquals(2, TestEventStorage.INSTANCE.queriesForScsIds(repo, commit)); publisher.startPublishing(); @@ -107,7 +107,7 @@ assertFalse(hub.getScsForCommit(repo, commit, List.of(branch)).isPresent()); assertEquals(1, TestEventStorage.INSTANCE.queriesForScsIds(repo, commit)); publisher.stopPublishing(); - hub.push(toCheck); + hub.push(toCheck, false); assertTrue(hub.getScsForCommit(repo, commit, List.of(branch)).isPresent()); assertEquals(1, TestEventStorage.INSTANCE.queriesForScsIds(repo, commit)); publisher.startPublishing(); @@ -120,7 +120,7 @@ List<EiffelSourceChangeCreatedEventInfo> toPublish = TestUtil.newSccs(5); publisher.stopPublishing(); for (EiffelEvent event : toPublish) { - hub.push(event); + hub.push(event, false); } publisher.startPublishing(); publisher.assertOrder(toPublish); @@ -131,8 +131,8 @@ EiffelSourceChangeCreatedEventInfo event = TestUtil.newScc(); EiffelEvent sccCopy = TestUtil.copyGitIdentifier(event, SCC); assertEquals(EventKey.fromEvent(event), EventKey.fromEvent(sccCopy)); - hub.push(event); - hub.push(sccCopy); + hub.push(event, false); + hub.push(sccCopy, false); EiffelEvent published = publisher.getPublished(EventKey.fromEvent(event)); assertNotEquals(sccCopy.meta.id, published.meta.id); assertEquals(event.meta.id, published.meta.id); @@ -144,8 +144,8 @@ EiffelEvent sccCopy = TestUtil.copyGitIdentifier(event, SCC); assertEquals(EventKey.fromEvent(event), EventKey.fromEvent(sccCopy)); publisher.stopPublishing(); - hub.push(event); - hub.push(sccCopy); + hub.push(event, false); + hub.push(sccCopy, false); publisher.startPublishing(); EiffelEvent published = publisher.getPublished(EventKey.fromEvent(event)); assertNotEquals(sccCopy.meta.id, published.meta.id); @@ -156,8 +156,8 @@ public void eventDoesNotReplaceAlreadyExistingWithDifferentType() throws Exception { EiffelSourceChangeCreatedEventInfo event = TestUtil.newScc(); EiffelEvent scsCopy = TestUtil.copyGitIdentifier(event, SCS); - hub.push(event); - hub.push(scsCopy); + hub.push(event, false); + hub.push(scsCopy, false); assertNotEquals(scsCopy.meta.id, event.meta.id); assertEquals(event.meta.id, publisher.getPublished(EventKey.fromEvent(event)).meta.id); assertEquals(scsCopy.meta.id, publisher.getPublished(EventKey.fromEvent(scsCopy)).meta.id); @@ -168,8 +168,8 @@ EiffelSourceChangeCreatedEventInfo event = TestUtil.newScc(); EiffelEvent scsCopy = TestUtil.copyGitIdentifier(event, SCS); publisher.stopPublishing(); - hub.push(event); - hub.push(scsCopy); + hub.push(event, false); + hub.push(scsCopy, false); publisher.startPublishing(); assertNotEquals(scsCopy.meta.id, event.meta.id); assertEquals(event.meta.id, publisher.getPublished(EventKey.fromEvent(event)).meta.id); @@ -180,7 +180,7 @@ public void idCacheIsUpdatedUponAck() throws Exception { publisher.stopPublishing(); EiffelSourceChangeCreatedEventInfo event = TestUtil.newScc(); - hub.push(event); + hub.push(event, false); EventKey key = EventKey.fromEvent(event); assertEquals(event.meta.id, hub.getExistingId(key).get()); assertTrue(idCache.getEventId(key).isEmpty());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java index b9a8208..8d08ad4 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java
@@ -25,7 +25,7 @@ import com.google.gerrit.extensions.api.projects.TagInput; import com.google.inject.Provider; import com.googlesource.gerrit.plugins.eventseiffel.config.EiffelConfig; -import com.googlesource.gerrit.plugins.eventseiffel.eiffel.CompositionDefinedEventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorageException; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkInfo; @@ -111,7 +111,7 @@ result.getCommit().getName()); } - protected UUID markAsHandled(CompositionDefinedEventKey key) throws EventStorageException { + protected UUID markAsHandled(EventKey key) throws EventStorageException { Optional<UUID> eventId = TestEventStorage.INSTANCE.getEventId(key); if (eventId.isPresent()) { return eventId.get();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java index 4601222..ddbb055 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/TestEventHub.java
@@ -40,13 +40,18 @@ } @Override - public void push(EiffelEvent event) { - EVENTS.add(event); + public void push(EiffelEvent event, boolean force) { + Optional<EiffelEvent> eventPushed = findEvent(EventKey.fromEvent(event)); + if (eventPushed.isEmpty()) EVENTS.add(event); + else if (force) { + EVENTS.remove(eventPushed.get()); + EVENTS.add(event); + } } @Override public Optional<UUID> getExistingId(EventKey key) throws EiffelEventIdLookupException { - Optional<UUID> eventId = find(key); + Optional<UUID> eventId = findId(key); if (eventId.isPresent()) { return eventId; } @@ -60,20 +65,24 @@ branches.stream() .map(branch -> SourceChangeEventKey.scsKey(repo, branch, commit)) .collect(Collectors.toList()); - Optional<UUID> id = keys.stream().map(key -> find(key)).flatMap(Optional::stream).findAny(); + Optional<UUID> id = keys.stream().map(key -> findId(key)).flatMap(Optional::stream).findAny(); if (id.isPresent()) return id; return idCache.getScsForCommit(repo, commit, branches); } - private Optional<UUID> find(EventKey key) { + private Optional<EiffelEvent> findEvent(EventKey key) { for (EiffelEvent event : EVENTS) { if (key.equals(EventKey.fromEvent(event))) { - return Optional.of(event.meta.id); + return Optional.of(event); } } return Optional.empty(); } + private Optional<UUID> findId(EventKey key) { + return findEvent(key).map(event -> event.meta.id); + } + @Override public Set<EiffelEvent> take(int max) throws InterruptedException { return null;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java index b0ed89b..9ff17be 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java
@@ -201,8 +201,49 @@ } @Test + public void artcNotRepublishedWithoutForce() throws Exception { + String ref = setCdHandled(); + ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), ref)); + markAsHandled(artc); + eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false); + assertEquals(0, TestEventHub.EVENTS.size()); + } + + @Test + public void artcRepublishedWithForce() throws Exception { + setScsHandled(); + String ref = + createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length()); + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(tagCompositionName(project.get()), ref); + markAsHandled(cd); + ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), ref)); + markAsHandled(artc); + eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, true); + assertEquals(2, TestEventHub.EVENTS.size()); + assertCorrectEvent(0, cd); + assertCorrectEvent(1, artc); + } + + private String setCdHandled() throws Exception { + setScsHandled(); + String ref = + createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length()); + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(tagCompositionName(project.get()), ref); + markAsHandled(cd); + return ref; + } + + private void setScsHandled() throws Exception { + SourceChangeEventKey scs = + SourceChangeEventKey.scsKey(project.get(), getHead(), getHeadRevision()); + markAsHandled(scs, getHead(repo(), "HEAD")); + } + + @Test public void artcQueued() throws Exception { - eventParser.createAndScheduleArtc(project.get(), TAG_NAME, EPOCH_MILLIS); + eventParser.createAndScheduleArtc(project.get(), TAG_NAME, EPOCH_MILLIS, false); assertEquals(0, TestEventHub.EVENTS.size()); } @@ -218,23 +259,15 @@ @Test public void artcQueuedcdHandled() throws Exception { - SourceChangeEventKey scs = - SourceChangeEventKey.scsKey(project.get(), getHead(), getHeadRevision()); - markAsHandled(scs, getHead(repo(), "HEAD")); - ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), TAG_NAME)); - CompositionDefinedEventKey cd = - CompositionDefinedEventKey.create(tagCompositionName(project.get()), TAG_NAME); - markAsHandled(cd); + String ref = setCdHandled(); + ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), ref)); - eventParser.createAndScheduleArtc(project.get(), TAG_NAME, EPOCH_MILLIS); - assertEquals(1, TestEventHub.EVENTS.size()); - assertCorrectEvent(0, artc); + eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false); + assertEquals(0, TestEventHub.EVENTS.size()); } private void assertArtcQueuedScsHandled(boolean annotated) throws Exception { - SourceChangeEventKey scs = - SourceChangeEventKey.scsKey(project.get(), getHead(), getHeadRevision()); - markAsHandled(scs, getHead(repo(), "HEAD")); + setScsHandled(); String ref = createTagRef(getHead(repo(), "HEAD").getName(), annotated) .substring(Constants.R_TAGS.length()); @@ -242,7 +275,7 @@ CompositionDefinedEventKey cd = CompositionDefinedEventKey.create(tagCompositionName(project.get()), ref); - eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS); + eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false); assertEquals(2, TestEventHub.EVENTS.size()); assertCorrectEvent(0, cd); assertCorrectEvent(1, artc);