Merge branch 'stable-3.4' * stable-3.4: (24 commits) ParsingQueuePersistenceIT: fix google-common imports EiffelEventParser: fix javadoc Persist failed tasks ParsingQueue: Save failed parsing-tasks Keep queued tasks to persist in a Set and not a List Correct spelling persistance -> persistence ParsingQueue: Move scheduling logic into worker Fix EiffelEventParserIt#artcQueued Refactor EiffelEventParser and implementations EiffelConfig: fix invalid log format EventKey: add missing cases to switch/case Fix event-name in routing key Persist parsing-queue on plugin stop EiffelEventParsingQueue: Google-java-format Refactor EiffelEventParserIT Make EiffelEventParsingQueue#scheduleArtcCreation private Extract private method to schedule SCS from submit-event Rename EiffelEventParser and EiffelEventParserIf Extract interface from EiffelEventParser Extract the task description from EventParsingWorker ... Change-Id: I8d6f26aef920b9b7ccd967a5f7f164a02b9c6650
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Manager.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Manager.java index 519698c..16d61c3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Manager.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Manager.java
@@ -31,6 +31,7 @@ @Override public void start() { eventHub.startPublishing(); + parsingQueue.init(); } @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java index 28649a0..9bdc0c4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java
@@ -39,6 +39,8 @@ import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory; import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; import com.googlesource.gerrit.plugins.eventseiffel.mq.RabbitMqPublisher; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserImpl; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingExecutor; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueue; import com.googlesource.gerrit.plugins.eventseiffel.rest.RestModule; @@ -98,6 +100,7 @@ .in(Scopes.SINGLETON); bind(EiffelEventParsingExecutor.Scheduled.class).in(Scopes.SINGLETON); bind(EiffelEventParsingExecutor.class).to(EiffelEventParsingExecutor.Scheduled.class); + bind(EiffelEventParser.class).to(EiffelEventParserImpl.class); bind(EiffelEventParsingQueue.class).in(Scopes.SINGLETON); bind(EiffelEventHubImpl.class).in(Scopes.SINGLETON); bind(EiffelEventHub.class).to(EiffelEventHubImpl.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EiffelConfig.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EiffelConfig.java index e04401a..7d43311 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EiffelConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EiffelConfig.java
@@ -51,7 +51,7 @@ logger.atInfo().log("namespace is not set in the config"); } else { this.namespace = Optional.of(namespace); - logger.atInfo().log("namespace is set to: %d", namespace); + logger.atInfo().log("namespace is set to: %s", namespace); } }
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 fe582d4..a8ca972 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
@@ -69,6 +69,8 @@ case ARTC: case CD: return true; + case SCC: + case SCS: default: return false; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/dto/EiffelEventType.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/dto/EiffelEventType.java index 6ea9a27..27b3ee4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/dto/EiffelEventType.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/dto/EiffelEventType.java
@@ -18,11 +18,11 @@ public enum EiffelEventType { @SerializedName("EiffelSourceChangeCreatedEvent") - SCC("EiffelSourceChangeCreated"), + SCC("EiffelSourceChangeCreatedEvent"), @SerializedName("EiffelSourceChangeSubmittedEvent") - SCS("EiffelSourceChangeSubmitted"), + SCS("EiffelSourceChangeSubmittedEvent"), @SerializedName("EiffelArtifactCreatedEvent") - ARTC("EiffelArtifactCreated"), + ARTC("EiffelArtifactCreatedEvent"), @SerializedName("EiffelCompositionDefinedEvent") CD("EiffelCompositionDefinedEvent");
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 d5d3ff0..faa1fed 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
@@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Android Open Source Project +// Copyright (C) 2022 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,451 +11,81 @@ // 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.googlesource.gerrit.plugins.eventseiffel.parsing; -import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; - -import com.github.rholder.retry.RetryException; -import com.github.rholder.retry.Retryer; -import com.github.rholder.retry.RetryerBuilder; -import com.github.rholder.retry.StopStrategies; -import com.github.rholder.retry.WaitStrategies; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import com.google.common.flogger.FluentLogger; -import com.google.gerrit.entities.Project; -import com.google.gerrit.entities.RefNames; -import com.google.gerrit.exceptions.NoSuchEntityException; import com.google.gerrit.extensions.common.AccountInfo; -import com.google.gerrit.extensions.common.CommitInfo; import com.google.gerrit.extensions.events.RevisionCreatedListener.Event; -import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; -import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException; -import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter; -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.dto.EiffelEvent; -import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; -import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.EventCreate; -import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.ScsWalker; -import java.io.IOException; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.RepositoryNotFoundException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.revwalk.RevWalkUtils; -/** Creates and pushes missing Eiffel events to the Eiffel event queue. */ -public class EiffelEventParser { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private static final int NBR_RETRIES = 3; - - private final EiffelEventHub eventHub; - private final GitRepositoryManager repoManager; - private final UnprocessedCommitsWalker.Factory walkerFactory; - private final EiffelEventMapper mapper; - private final Provider<EventsFilter> eventsFilter; - - @Inject - public EiffelEventParser( - EiffelEventHub eventQueue, - GitRepositoryManager repoManager, - EiffelEventMapper mapper, - UnprocessedCommitsWalker.Factory walkerFactory, - Provider<EventsFilter> eventsFilter) { - this.eventHub = eventQueue; - this.repoManager = repoManager; - this.walkerFactory = walkerFactory; - this.mapper = mapper; - this.eventsFilter = eventsFilter; - } - - public void createAndScheduleSccFromEvent(Event event) { - CommitInfo commit = event.getRevision().commit; - SourceChangeEventKey scc = - SourceChangeEventKey.sccKey( - event.getChange().project, event.getChange().branch, commit.commit); - try { - if (eventHub.getExistingId(scc).isPresent()) { - logger.atWarning().log( - "Event %s already pushed for %d/%d", - scc, event.getChange()._number, event.getRevision()._number); - return; - } - List<UUID> parentUuids = Lists.newArrayList(); - for (CommitInfo parent : commit.parents) { - Optional<UUID> parentUuid = eventHub.getExistingId(scc.copy(parent.commit)); - if (parentUuid.isPresent()) { - parentUuids.add(parentUuid.get()); - } - } - - /* Eiffel events have been scheduled or published for all parents. */ - if (parentUuids.size() == commit.parents.size()) { - pushToHub(mapper.toScc(event, parentUuids)); - } else { - createAndScheduleMissingSccs(scc); - } - } catch (IOException - | ConfigInvalidException - | NoSuchEntityException - | EiffelEventIdLookupException - | InterruptedException e) { - logger.atSevere().withCause(e).log( - "Event creation failed for: %s, %s, %s to SCC.", - event.getChange().project, event.getChange().branch, event.getRevision().commit.commit); - } - } +public interface EiffelEventParser { /** - * Creates SCC events from a ref. ref can be a branch or a patch-set ref. + * Creates SCC events for all commits reachable from the commit that triggered the {@link Event} + * event. * - * @param repoName - * @param ref - schedule event creation for commits reachable from this ref. - * @param targetBranch - used to populate data.gitIdentifier.branch. + * @param event - Describes a revision from which to create SCC events. + * @throws EventParsingException- When event-creation fails. */ - public void createAndScheduleSccFromRef(String repoName, String ref, String targetBranch) { - ObjectId tip = getTipOf(repoName, ref); - if (tip == null) { - return; - } + void createAndScheduleSccFromEvent(Event event) throws EventParsingException; - SourceChangeEventKey scc = SourceChangeEventKey.sccKey(repoName, targetBranch, tip.getName()); - try { - createAndScheduleMissingSccs(scc); - } catch (IOException - | EiffelEventIdLookupException - | NoSuchEntityException - | ConfigInvalidException - | InterruptedException e) { - logger.atSevere().withCause(e).log("Event creation failed for: %s", scc); - } - } + /** + * Creates SCC events for all commits reachable from branchRef. I.e. create SCCs for already + * merged commit that are missing SCCs. + * + * @param repoName - Name of the repository where the branch exists. + * @param branchRef - Creates missing events for all commits reachable from branch. + * @throws EventParsingException - When event-creation fails. + */ + void createAndScheduleSccFromBranch(String repoName, String branchRef) + throws EventParsingException; - public void createAndScheduleMissingScssFromBranch(String repoName, String branch) { - ObjectId tip = getTipOf(repoName, branch); - if (tip == null) { - return; - } - SourceChangeEventKey scs = SourceChangeEventKey.scsKey(repoName, branch, tip.getName()); - createAndScheduleMissingScss(scs, null, null, null); - } + /** + * Creates SCC events for all commits reachable from commit. + * + * @param repoName - Name of the repository were the commits exists. + * @param branchRef - Ref of the branch to create events for. + * @param commit - Creates missing events for all commits reachable from commit. + * @throws EventParsingException - When event-creation fails. + */ + void createAndScheduleSccFromCommit(String repoName, String branchRef, String commit) + throws EventParsingException; - public void createAndScheduleMissingScss( + /** + * Creates missing SCS events for repoName, branch. + * + * @param repoName - Name of the repository where the branch exists. + * @param branchRef - Creates missing events for all commits reachable from branchRef. + * @throws EventParsingException - When event-creation fails. + */ + void createAndScheduleMissingScssFromBranch(String repoName, String branchRef) + throws EventParsingException; + + /** + * Creates missing SCS events for a submit transaction. submitter and submittedAt is set for all + * events created within the submit transaction, i.e. not reachable from commitSha1TransactionEnd. + * + * @param scs - Key for the event that should be created, together with parents. + * @param commitSha1TransactionEnd - Tip before submit transaction. + * @param submitter - The submitter + * @param submittedAt - When the commit was submitted. + * @throws EventParsingException - When event-creation fails. + */ + void createAndScheduleMissingScss( SourceChangeEventKey scs, String commitSha1TransactionEnd, AccountInfo submitter, - Long submittedAt) { - SourceChangeEventKey currentScs = scs; - SourceChangeEventKey scc = scs.copy(SCC); - try { - try (ScsWalker scsFinder = - walkerFactory.scsWalker(scs, commitSha1TransactionEnd, submitter, submittedAt)) { + Long submittedAt) + throws EventParsingException; - if (eventHub.getExistingId(scc).isEmpty()) { - /* One or several SCC events are missing, create them first */ - try (UnprocessedCommitsWalker sccFinder = scsFinder.sccWalker()) { - createAndScheduleMissingSccs(scc, sccFinder); - } - } - - if (!scsFinder.hasNext()) { - logger.atInfo().log("All events were already published for %s", scs); - } else { - logger.atFine().log("Start publishing events for: %s", scs); - } - while (scsFinder.hasNext()) { - EventCreate create = scsFinder.next(); - currentScs = create.key; - Optional<UUID> sccId = eventHub.getExistingId(create.key.copy(SCC)); - if (sccId.isEmpty()) { - throw new EiffelEventIdLookupException( - "Unable to find SCC event id: %s", create.key.copy(SCC)); - } - List<UUID> scsParentEventIds = Lists.newArrayList(); - for (RevCommit parent : create.commit.getParents()) { - Optional<UUID> parentUuid = eventHub.getExistingId(create.key.copy(parent.getName())); - if (parentUuid.isPresent()) { - scsParentEventIds.add(parentUuid.get()); - } else { - exceptionForMissingParent(create, parent); - } - } - pushToHub( - mapper.toScs( - create.commit, - create.key.repo(), - create.key.branch(), - create.submitter, - create.submittedAt, - scsParentEventIds, - sccId.get())); - } - } - logger.atFine().log("Done publishing events for: %s", scs); - } catch (IOException - | EiffelEventIdLookupException - | InterruptedException - | ConfigInvalidException - | NoSuchEntityException e) { - logger.atSevere().withCause(e).log("Failed to create Eiffel event(s) for %s.", currentScs); - } - } - - public void createAndScheduleArtc( - String projectName, String tagName, Long creationTime, boolean force) { - try { - 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( - "Event creation failed for: %s, %s to Artc", projectName, tagName); - } - } - - private void createAndScheduleCd( - String projectName, String tagName, Long creationTime, boolean force) { - Optional<UUID> scsId = Optional.empty(); - List<Ref> refs = null; - - try { - EventsFilter filter = eventsFilter.get(); - ObjectId objectId = peelTag(projectName, tagName, filter.blockLightWeightTags()); - if (objectId == null) { - return; - } - String commitId = objectId.getName(); - - /* Check if an event for commit~master has been created. */ - SourceChangeEventKey scs = - SourceChangeEventKey.scsKey(projectName, RefNames.REFS_HEADS + "master", commitId); - scsId = eventHub.getExistingId(scs); - - if (scsId.isEmpty()) { - /* No event created for commit~master. Check if event is created for any - of the other branches. */ - Retryer<Optional<UUID>> retryer = - RetryerBuilder.<Optional<UUID>>newBuilder() - .retryIfResult(Optional::isEmpty) - .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS)) - .withStopStrategy(StopStrategies.stopAfterAttempt(2)) - .build(); - try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { - refs = - repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_HEADS).stream() - .collect(Collectors.toList()); - } catch (IOException e) { - logger.atSevere().withCause(e).log( - "Unable to get branches for: %s:%s", projectName, tagName); - return; - } - try { - List<String> branches = - refs.stream() - .map(Ref::getName) - .filter(branch -> !branch.equals(RefNames.REFS_HEADS + "master")) - .collect(Collectors.toList()); - scsId = retryer.call(() -> findSourceChangeEventKey(projectName, commitId, branches)); - } catch (RetryException | ExecutionException e) { - logger.atWarning().withCause(e).log( - "Failed to find SCS for %s in %s when trying to create CD for tag %s", - commitId, projectName, tagName); - } - } - - if (scsId.isEmpty()) { - /* No event has been created for the commit. Find any non-blocked branch that - commit is merged into and create SCS events for that branch. */ - List<Ref> branches; - try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { - RevWalk rw = new RevWalk(repo); - refs = - refs.stream() - .filter(ref -> !filter.refIsBlocked(ref.getName())) - .collect(Collectors.toList()); - branches = RevWalkUtils.findBranchesReachableFrom(rw.parseCommit(objectId), rw, refs); - } catch (IOException e) { - logger.atSevere().withCause(e).log( - "Unable to get reachable branches for: %s:%s", projectName, tagName); - return; - } - if (branches.isEmpty()) { - logger.atWarning().log( - "Could not find any unblocked branch for SCS with: %s in %s so CD could not be created for tag %s", - commitId, projectName, tagName); - return; - } - String branch = branches.get(0).getName(); - scs = SourceChangeEventKey.scsKey(projectName, branch, commitId); - createAndScheduleMissingScss(scs, null, null, null); - scsId = eventHub.getExistingId(scs); - } - - if (scsId.isEmpty()) { - logger.atWarning().log( - "Could not find or create SCS for %s in %s so CD could not be created for tag %s", - commitId, projectName, tagName); - return; - } - pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get()), force); - } catch (EiffelEventIdLookupException | InterruptedException e) { - logger.atSevere().withCause(e).log( - "Event creation failed for: %s", - CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName)); - } - } - - private ObjectId peelTag(String projectName, String tagName, boolean blockLightWeightTags) { - try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { - Ref tagRef = repo.getRefDatabase().exactRef(Constants.R_TAGS + tagName); - if (tagRef != null) { - ObjectId peeled = repo.getRefDatabase().peel(tagRef).getPeeledObjectId(); - if (peeled != null) return peeled; - if (!blockLightWeightTags) return tagRef.getObjectId(); - logger.atInfo().log("Creation of CD is blocked for lightweight tags"); - return null; - } - logger.atSevere().log("Cannot find tag: %s:%s", projectName, tagName); - } catch (IOException e) { - logger.atSevere().withCause(e).log("Unable to peel tag: %s:%s", projectName, tagName); - } - return null; - } - - private Optional<UUID> findSourceChangeEventKey( - String projectName, String commitId, List<String> branches) - throws EiffelEventIdLookupException { - return eventHub.getScsForCommit(projectName, commitId, branches); - } - - 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, force); - logger.atFine().log("Successfully pushed %s to EventHub", key); - return; - } catch (InterruptedException e) { - if (!eventHub.isOpen()) { - logger.atInfo().log("EventHub is closed, aborting."); - throw e; - } - failureCount++; - if (failureCount < NBR_RETRIES) { - logger.atWarning().withCause(e).log( - "Interrupted while pushing %s to EventHub, attempt %d/%d", - key, failureCount, NBR_RETRIES); - } else { - throw e; - } - } - } - } - - @VisibleForTesting - void createAndScheduleMissingSccs(SourceChangeEventKey scc) - throws MissingObjectException, IncorrectObjectTypeException, IOException, - EiffelEventIdLookupException, RepositoryNotFoundException, NoSuchEntityException, - ConfigInvalidException, InterruptedException { - logger.atFine().log("Start publishing events for: %s", scc); - try (UnprocessedCommitsWalker commitFinder = walkerFactory.sccWalker(scc)) { - createAndScheduleMissingSccs(scc, commitFinder); - } - logger.atFine().log("Done publishing events for: %s", scc); - } - - /* Callers are responsible for closing commitFinder. */ - private void createAndScheduleMissingSccs( - SourceChangeEventKey scc, UnprocessedCommitsWalker commitFinder) - throws MissingObjectException, EiffelEventIdLookupException, IOException, - NoSuchEntityException, ConfigInvalidException, InterruptedException { - if (!commitFinder.hasNext()) { - logger.atInfo().log("All events were already published for %s", scc); - } else { - logger.atFine().log("Start publishing events for: %s", scc); - } - while (commitFinder.hasNext()) { - EventCreate job = commitFinder.next(); - logger.atFine().log("Processing event-creation for: %s", job.key); - List<UUID> parentIds = Lists.newArrayList(); - for (RevCommit parent : job.commit.getParents()) { - SourceChangeEventKey parentKey = scc.copy(parent.getName()); - Optional<UUID> parentId = eventHub.getExistingId(parentKey); - if (parentId.isPresent()) { - parentIds.add(parentId.get()); - } else { - exceptionForMissingParent(job, parent); - } - } - try { - pushToHub(mapper.toScc(job.commit, job.key.repo(), job.key.branch(), parentIds)); - } catch (InterruptedException e) { - logger.atSevere().log("Interrupted while pushing %s to EventHub.", job.key); - throw e; - } - } - } - - private ObjectId getTipOf(String repoName, String branch) { - try (Repository repo = repoManager.openRepository(Project.nameKey(repoName))) { - Ref branchRef = repo.exactRef(branch); - if (branchRef == null) { - logger.atWarning().log("Could not find ref: %s in project: %s", branch, repoName); - return null; - } - return branchRef.getTarget().getObjectId(); - } catch (IOException ioe) { - logger.atSevere().withCause(ioe).log("Unable to identify tip of (%s:%s).", repoName, branch); - return null; - } - } - - private void exceptionForMissingParent(EventCreate create, RevCommit parent) - throws NoSuchEntityException { - throw new NoSuchEntityException( - String.format( - "Unable to lookup parent (%s) event UUID for %s even though it should exist.", - parent.abbreviate(7).name(), create.key)); - } + /** + * Creates missing ARTC for a tag, together with CD and (if missing) SCS for referenced commit. + * + * @param repoName - Name of the repository were the tag exists. + * @param tagName - The name of the tag. + * @param creationTime - The time at which the tag was created. + * @param force - Whether existing events should be replaced or not. + * @throws EventParsingException - When event-creation fails. + */ + void createAndScheduleArtc(String repoName, String tagName, Long creationTime, boolean force) + throws EventParsingException; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java new file mode 100644 index 0000000..4e71eb1 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java
@@ -0,0 +1,488 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; + +import com.github.rholder.retry.RetryException; +import com.github.rholder.retry.Retryer; +import com.github.rholder.retry.RetryerBuilder; +import com.github.rholder.retry.StopStrategies; +import com.github.rholder.retry.WaitStrategies; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.RefNames; +import com.google.gerrit.exceptions.NoSuchEntityException; +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.common.CommitInfo; +import com.google.gerrit.extensions.events.RevisionCreatedListener.Event; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; +import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException; +import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter; +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.dto.EiffelEvent; +import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.EventCreate; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.UnprocessedCommitsWalker.ScsWalker; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.RevWalkUtils; + +/** Creates and pushes missing Eiffel events to the Eiffel event queue. */ +public class EiffelEventParserImpl implements EiffelEventParser { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private static final int NBR_RETRIES = 3; + + private final EiffelEventHub eventHub; + private final GitRepositoryManager repoManager; + private final UnprocessedCommitsWalker.Factory walkerFactory; + private final EiffelEventMapper mapper; + private final Provider<EventsFilter> eventsFilter; + + @Inject + public EiffelEventParserImpl( + EiffelEventHub eventQueue, + GitRepositoryManager repoManager, + EiffelEventMapper mapper, + UnprocessedCommitsWalker.Factory walkerFactory, + Provider<EventsFilter> eventsFilter) { + this.eventHub = eventQueue; + this.repoManager = repoManager; + this.walkerFactory = walkerFactory; + this.mapper = mapper; + this.eventsFilter = eventsFilter; + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleSccFromEvent(com.google.gerrit.extensions.events.RevisionCreatedListener.Event) + */ + @Override + public void createAndScheduleSccFromEvent(Event event) throws EventParsingException { + CommitInfo commit = event.getRevision().commit; + SourceChangeEventKey scc = + SourceChangeEventKey.sccKey( + event.getChange().project, event.getChange().branch, commit.commit); + try { + if (eventHub.getExistingId(scc).isPresent()) { + logger.atWarning().log( + "Event %s already pushed for %d/%d", + scc, event.getChange()._number, event.getRevision()._number); + return; + } + List<UUID> parentUuids = Lists.newArrayList(); + for (CommitInfo parent : commit.parents) { + Optional<UUID> parentUuid = eventHub.getExistingId(scc.copy(parent.commit)); + if (parentUuid.isPresent()) { + parentUuids.add(parentUuid.get()); + } + } + + /* Eiffel events have been scheduled or published for all parents. */ + if (parentUuids.size() == commit.parents.size()) { + pushToHub(mapper.toScc(event, parentUuids)); + } else { + createAndScheduleMissingSccs(scc); + } + } catch (IOException + | ConfigInvalidException + | NoSuchEntityException + | EiffelEventIdLookupException + | InterruptedException e) { + throw new EventParsingException( + e, + "Event creation failed for: %s, %s, %s to SCC.", + event.getChange().project, + event.getChange().branch, + event.getRevision().commit.commit); + } + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleSccFromBranch(java.lang.String, java.lang.String) + */ + @Override + public void createAndScheduleSccFromBranch(String repoName, String branchRef) + throws EventParsingException { + ObjectId tip = getTipOf(repoName, branchRef); + if (tip == null) { + return; + } + createAndScheduleSccFromCommit(repoName, branchRef, tip.getName()); + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleSccFromCommit(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void createAndScheduleSccFromCommit(String repoName, String branchRef, String commit) + throws EventParsingException { + + SourceChangeEventKey scc = SourceChangeEventKey.sccKey(repoName, branchRef, commit); + try { + createAndScheduleMissingSccs(scc); + } catch (IOException + | EiffelEventIdLookupException + | NoSuchEntityException + | ConfigInvalidException + | InterruptedException e) { + throw new EventParsingException(e, "Event creation failed for: %s", scc); + } + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleMissingScssFromBranch(java.lang.String, java.lang.String) + */ + @Override + public void createAndScheduleMissingScssFromBranch(String repoName, String branchRef) + throws EventParsingException { + ObjectId tip = getTipOf(repoName, branchRef); + if (tip == null) { + return; + } + SourceChangeEventKey scs = SourceChangeEventKey.scsKey(repoName, branchRef, tip.getName()); + createAndScheduleMissingScss(scs, null, null, null); + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleMissingScss(com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey, java.lang.String, com.google.gerrit.extensions.common.AccountInfo, java.lang.Long) + */ + @Override + public void createAndScheduleMissingScss( + SourceChangeEventKey scs, + String commitSha1TransactionEnd, + AccountInfo submitter, + Long submittedAt) + throws EventParsingException { + SourceChangeEventKey currentScs = scs; + SourceChangeEventKey scc = scs.copy(SCC); + try { + try (ScsWalker scsFinder = + walkerFactory.scsWalker(scs, commitSha1TransactionEnd, submitter, submittedAt)) { + + if (eventHub.getExistingId(scc).isEmpty()) { + /* One or several SCC events are missing, create them first */ + try (UnprocessedCommitsWalker sccFinder = scsFinder.sccWalker()) { + createAndScheduleMissingSccs(scc, sccFinder); + } + } + + if (!scsFinder.hasNext()) { + logger.atInfo().log("All events were already published for %s", scs); + } else { + logger.atFine().log("Start publishing events for: %s", scs); + } + while (scsFinder.hasNext()) { + EventCreate create = scsFinder.next(); + currentScs = create.key; + Optional<UUID> sccId = eventHub.getExistingId(create.key.copy(SCC)); + if (sccId.isEmpty()) { + throw new EiffelEventIdLookupException( + "Unable to find SCC event id: %s", create.key.copy(SCC)); + } + List<UUID> scsParentEventIds = Lists.newArrayList(); + for (RevCommit parent : create.commit.getParents()) { + Optional<UUID> parentUuid = eventHub.getExistingId(create.key.copy(parent.getName())); + if (parentUuid.isPresent()) { + scsParentEventIds.add(parentUuid.get()); + } else { + exceptionForMissingParent(create, parent); + } + } + pushToHub( + mapper.toScs( + create.commit, + create.key.repo(), + create.key.branch(), + create.submitter, + create.submittedAt, + scsParentEventIds, + sccId.get())); + } + } + logger.atFine().log("Done publishing events for: %s", scs); + } catch (IOException + | EiffelEventIdLookupException + | InterruptedException + | ConfigInvalidException + | NoSuchEntityException e) { + throw new EventParsingException(e, "Failed to create Eiffel event(s) for %s.", currentScs); + } + } + + /* (non-Javadoc) + * @see com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser#createAndScheduleArtc(java.lang.String, java.lang.String, java.lang.Long, boolean) + */ + @Override + public void createAndScheduleArtc( + String repoName, String tagName, Long creationTime, boolean force) + throws EventParsingException { + try { + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(mapper.tagCompositionName(repoName), tagName); + Optional<UUID> oldCdId = eventHub.getExistingId(cd); + if (oldCdId.isEmpty() || force) { + createAndScheduleCd(repoName, tagName, creationTime, force); + Optional<UUID> cdId = eventHub.getExistingId(cd); + if (cdId.isPresent() && !cdId.equals(oldCdId)) { + pushToHub(mapper.toArtc(repoName, tagName, creationTime, cdId.get()), force); + if (oldCdId.isPresent()) { + logger.atInfo().log( + "Event Artc has been forcibly created for: %s, %s", repoName, tagName); + } else { + logger.atFine().log("Event Artc has been created for: %s, %s", repoName, tagName); + } + } + } else { + /* Artc event has already been created */ + logger.atWarning().log( + "Event Artc has already been created for: %s, %s", repoName, tagName); + } + } catch (EiffelEventIdLookupException | InterruptedException e) { + throw new EventParsingException( + e, "Event creation failed for: %s, %s to Artc", repoName, tagName); + } + } + + private void createAndScheduleCd( + String projectName, String tagName, Long creationTime, boolean force) + throws EventParsingException { + Optional<UUID> scsId = Optional.empty(); + List<Ref> refs = null; + + try { + EventsFilter filter = eventsFilter.get(); + ObjectId objectId = peelTag(projectName, tagName, filter.blockLightWeightTags()); + if (objectId == null) { + return; + } + String commitId = objectId.getName(); + + /* Check if an event for commit~master has been created. */ + SourceChangeEventKey scs = + SourceChangeEventKey.scsKey(projectName, RefNames.REFS_HEADS + "master", commitId); + scsId = eventHub.getExistingId(scs); + + if (scsId.isEmpty()) { + /* No event created for commit~master. Check if event is created for any + of the other branches. */ + Retryer<Optional<UUID>> retryer = + RetryerBuilder.<Optional<UUID>>newBuilder() + .retryIfResult(Optional::isEmpty) + .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS)) + .withStopStrategy(StopStrategies.stopAfterAttempt(2)) + .build(); + try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { + refs = + repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_HEADS).stream() + .collect(Collectors.toList()); + } catch (IOException e) { + throw new EventParsingException( + e, "Unable to get branches for: %s:%s", projectName, tagName); + } + try { + List<String> branches = + refs.stream() + .map(Ref::getName) + .filter(branch -> !branch.equals(RefNames.REFS_HEADS + "master")) + .collect(Collectors.toList()); + scsId = retryer.call(() -> findSourceChangeEventKey(projectName, commitId, branches)); + } catch (RetryException | ExecutionException e) { + logger.atWarning().withCause(e).log( + "Failed to find SCS for %s in %s when trying to create CD for tag %s", + commitId, projectName, tagName); + } + } + + if (scsId.isEmpty()) { + /* No event has been created for the commit. Find any non-blocked branch that + commit is merged into and create SCS events for that branch. */ + List<Ref> branches; + try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { + RevWalk rw = new RevWalk(repo); + refs = + refs.stream() + .filter(ref -> !filter.refIsBlocked(ref.getName())) + .collect(Collectors.toList()); + branches = RevWalkUtils.findBranchesReachableFrom(rw.parseCommit(objectId), rw, refs); + } catch (IOException e) { + throw new EventParsingException( + e, "Unable to get reachable branches for: %s:%s", projectName, tagName); + } + if (branches.isEmpty()) { + throw new EventParsingException( + "Could not find any unblocked branch for SCS with: %s in %s so CD could not be created for tag %s", + commitId, projectName, tagName); + } + String branch = branches.get(0).getName(); + scs = SourceChangeEventKey.scsKey(projectName, branch, commitId); + createAndScheduleMissingScss(scs, null, null, null); + scsId = eventHub.getExistingId(scs); + } + + if (scsId.isEmpty()) { + throw new EventParsingException( + "Could not find or create SCS for %s in %s so CD could not be created for tag %s", + commitId, projectName, tagName); + } + pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get()), force); + } catch (EiffelEventIdLookupException | InterruptedException e) { + throw new EventParsingException( + e, + "Event creation failed for: %s", + CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName)); + } + } + + private ObjectId peelTag(String projectName, String tagName, boolean blockLightWeightTags) + throws EventParsingException { + try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { + Ref tagRef = repo.getRefDatabase().exactRef(Constants.R_TAGS + tagName); + if (tagRef != null) { + ObjectId peeled = repo.getRefDatabase().peel(tagRef).getPeeledObjectId(); + if (peeled != null) return peeled; + if (!blockLightWeightTags) return tagRef.getObjectId(); + logger.atInfo().log("Creation of CD is blocked for lightweight tags"); + return null; + } + throw new EventParsingException("Cannot find tag: %s:%s", projectName, tagName); + } catch (IOException e) { + throw new EventParsingException(e, "Unable to peel tag: %s:%s", projectName, tagName); + } + } + + private Optional<UUID> findSourceChangeEventKey( + String projectName, String commitId, List<String> branches) + throws EiffelEventIdLookupException { + return eventHub.getScsForCommit(projectName, commitId, branches); + } + + 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, force); + logger.atFine().log("Successfully pushed %s to EventHub", key); + return; + } catch (InterruptedException e) { + if (!eventHub.isOpen()) { + logger.atInfo().log("EventHub is closed, aborting."); + throw e; + } + failureCount++; + if (failureCount < NBR_RETRIES) { + logger.atWarning().withCause(e).log( + "Interrupted while pushing %s to EventHub, attempt %d/%d", + key, failureCount, NBR_RETRIES); + } else { + throw e; + } + } + } + } + + @VisibleForTesting + void createAndScheduleMissingSccs(SourceChangeEventKey scc) + throws MissingObjectException, IncorrectObjectTypeException, IOException, + EiffelEventIdLookupException, RepositoryNotFoundException, NoSuchEntityException, + ConfigInvalidException, InterruptedException { + logger.atFine().log("Start publishing events for: %s", scc); + try (UnprocessedCommitsWalker commitFinder = walkerFactory.sccWalker(scc)) { + createAndScheduleMissingSccs(scc, commitFinder); + } + logger.atFine().log("Done publishing events for: %s", scc); + } + + /* Callers are responsible for closing commitFinder. */ + private void createAndScheduleMissingSccs( + SourceChangeEventKey scc, UnprocessedCommitsWalker commitFinder) + throws MissingObjectException, EiffelEventIdLookupException, IOException, + NoSuchEntityException, ConfigInvalidException, InterruptedException { + if (!commitFinder.hasNext()) { + logger.atInfo().log("All events were already published for %s", scc); + } else { + logger.atFine().log("Start publishing events for: %s", scc); + } + while (commitFinder.hasNext()) { + EventCreate job = commitFinder.next(); + logger.atFine().log("Processing event-creation for: %s", job.key); + List<UUID> parentIds = Lists.newArrayList(); + for (RevCommit parent : job.commit.getParents()) { + SourceChangeEventKey parentKey = scc.copy(parent.getName()); + Optional<UUID> parentId = eventHub.getExistingId(parentKey); + if (parentId.isPresent()) { + parentIds.add(parentId.get()); + } else { + exceptionForMissingParent(job, parent); + } + } + try { + pushToHub(mapper.toScc(job.commit, job.key.repo(), job.key.branch(), parentIds)); + } catch (InterruptedException e) { + logger.atSevere().log("Interrupted while pushing %s to EventHub.", job.key); + throw e; + } + } + } + + private ObjectId getTipOf(String repoName, String branch) { + try (Repository repo = repoManager.openRepository(Project.nameKey(repoName))) { + Ref branchRef = repo.exactRef(branch); + if (branchRef == null) { + logger.atWarning().log("Could not find ref: %s in project: %s", branch, repoName); + return null; + } + return branchRef.getTarget().getObjectId(); + } catch (IOException ioe) { + logger.atSevere().withCause(ioe).log("Unable to identify tip of (%s:%s).", repoName, branch); + return null; + } + } + + private void exceptionForMissingParent(EventCreate create, RevCommit parent) + throws NoSuchEntityException { + throw new NoSuchEntityException( + String.format( + "Unable to lookup parent (%s) event UUID for %s even though it should exist.", + parent.abbreviate(7).name(), create.key)); + } +}
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 1fd7ec5..01d5233 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
@@ -18,10 +18,11 @@ import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCS; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Sets; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Project; import com.google.gerrit.entities.Project.NameKey; -import com.google.gerrit.entities.RefNames; import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; import com.google.gerrit.extensions.events.RevisionCreatedListener; import com.google.gerrit.server.git.ProjectRunnable; @@ -29,7 +30,9 @@ import com.google.gerrit.server.util.time.TimeUtil; import com.google.inject.Inject; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; -import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; @@ -38,188 +41,186 @@ private volatile EiffelEventParsingExecutor pool; private final EiffelEventParser eventParser; + private final ParsingQueuePersistence persistence; private final ConcurrentHashMap<EventParsingWorker, ScheduledFuture<?>> pending = new ConcurrentHashMap<>(); + private final Set<ParsingQueueTask> failedTasks = new HashSet<>(); + private final Object failedTasksLock = new Object(); @Inject - public EiffelEventParsingQueue(EiffelEventParsingExecutor pool, EiffelEventParser eventParser) { + public EiffelEventParsingQueue( + EiffelEventParsingExecutor pool, + EiffelEventParser eventParser, + ParsingQueuePersistence persistence) { this.pool = pool; this.eventParser = eventParser; + this.persistence = persistence; } public void scheduleSccCreation(RevisionCreatedListener.Event event) { - schedule( - new EventParsingWorker( - SCC, - event.getChange().project, - event.getChange().branch, - event.getRevision().commit.commit) { - - @Override - public void doRun() { - try { - eventParser.createAndScheduleSccFromEvent(event); - } catch (Exception e) { - logger.atSevere().withCause(e).log( - "Failed to create SCC from event for %s:%s:%s.", - event.getChange().project, event.getChange().branch, event.getRevision().commit); - } - } - }); + schedule(ParsingQueueTask.sccBuilder(event)); } /** - * Schedule SCC creation from a branch ref. This is the special case where the ref that the - * commits, that the events that are supposed to be created from, are reachable from is the same - * as the target Branch. + * Schedules SCC creation for all commits reachable from branchRef. * - * @param repoName - * @param branch + * @param repoName - the repository containing the commits to create events from. + * @param branchRef - create events for commits reachable from tip of branchRef. */ - public void scheduleSccCreation(String repoName, String branch) { - scheduleSccCreation(repoName, branch, branch); + public void scheduleSccCreation(String repoName, String branchRef) { + schedule(ParsingQueueTask.builder(SCC, repoName, branchRef)); } /** - * Schedule SCC creation from a ref. ref can be a branch or a patch-set ref. + * Schedule SCC creation for all commits reachable from commit. * - * @param repoName - * @param ref - schedule event creation for commits reachable from this ref. - * @param targetBranch - used to populate data.gitIdentifier.branch. + * @param repoName - the repository containing the commits to create events from. + * @param branchRef - used to populate data.gitIdentifier.branch. + * @param commit - create events from commits reachable from commit. */ - public void scheduleSccCreation(String repoName, String ref, String targetBranch) { - schedule( - new EventParsingWorker(SCC, repoName, targetBranch, null) { - - @Override - public void doRun() { - try { - eventParser.createAndScheduleSccFromRef(repoName, ref, targetBranch); - } catch (Exception e) { - logger.atSevere().withCause(e).log( - "Failed to create SCC for %s:%s:%s.", repoName, ref, targetBranch); - } - } - }); + public void scheduleSccCreation(String repoName, String branchRef, String commit) { + schedule(ParsingQueueTask.builder(SCC, repoName, branchRef).commit(commit)); } public void scheduleScsCreation(GitReferenceUpdatedListener.Event event) { schedule( - new EventParsingWorker( - SCS, event.getProjectName(), event.getRefName(), event.getNewObjectId()) { - - @Override - public void doRun() { - try { - eventParser.createAndScheduleMissingScss( - SourceChangeEventKey.scsKey( - event.getProjectName(), event.getRefName(), event.getNewObjectId()), - event.getOldObjectId(), - event.getUpdater(), - TimeUtil.nowMs()); - } catch (Exception e) { - logger.atSevere().withCause(e).log( - "Failed to create SCS from event for %s:%s:%s.", - event.getProjectName(), event.getRefName(), event.getNewObjectId()); - } - } - }); + ParsingQueueTask.builder(SCS, event.getProjectName(), event.getRefName()) + .commit(event.getNewObjectId()) + .updater(event.getUpdater()) + .updateTime(TimeUtil.nowMs()) + .previousTip(event.getOldObjectId())); } - public void scheduleScsCreation(String repoName, String branch) { - schedule( - new EventParsingWorker(SCS, repoName, branch, null) { - - @Override - public void doRun() { - try { - eventParser.createAndScheduleMissingScssFromBranch(repoName, branch); - } catch (Exception e) { - logger.atSevere().withCause(e).log( - "Failed to create SCS for %s:%s", repoName, branch); - } - } - }); + public void scheduleScsCreation(String repoName, String branchRef) { + schedule(ParsingQueueTask.builder(SCS, repoName, branchRef)); } public void scheduleArtcCreation(GitReferenceUpdatedListener.Event event) { - scheduleArtcCreation( - event.getProjectName(), - event.getRefName().substring(RefNames.REFS_TAGS.length()), - TimeUtil.nowMs(), - false); + schedule( + ParsingQueueTask.builder(ARTC, event.getProjectName(), event.getRefName()) + .updateTime(TimeUtil.nowMs()) + .force(false)); } 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(), - force); - } - - 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, force); - } catch (Exception e) { - logger.atSevere().withCause(e).log( - "Failed to create ARTC for %s:%s", projectName, tagName); - } - } - }); + ParsingQueueTask.builder(ARTC, resource.getName(), resource.getRef()) + .updateTime(resource.getTagInfo().created.getTime()) + .force(force)); } public void shutDown() { - for (ScheduledFuture<?> f : pending.values()) { - f.cancel(true); + Set<ParsingQueueTask> tasks = Sets.newHashSet(); + for (Map.Entry<EventParsingWorker, ScheduledFuture<?>> e : pending.entrySet()) { + e.getValue().cancel(true); + tasks.add(e.getKey().task); } + persistence.persistQueue(tasks); + persistence.persistFailed(failedTasks); pool.shutDown(); } - private void schedule(EventParsingWorker worker) { + public void init() { + for (ParsingQueueTask task : persistence.getFailedTasks()) { + schedule(task); + } + for (ParsingQueueTask task : persistence.getPersistedQueue()) { + schedule(task); + } + } + + /** + * Reschedules all failed tasks. + * + * @return the number of failed tasks that were rescheduled. + */ + public int rescheduleFailed() { + int failedCnt = 0; + synchronized (failedTasksLock) { + failedCnt = failedTasks.size(); + for (ParsingQueueTask task : failedTasks) { + schedule(task); + } + failedTasks.clear(); + } + return failedCnt; + } + + protected void markAsCompleted(EventParsingWorker worker) { + pending.remove(worker); + if (!worker.successful) { + synchronized (failedTasksLock) { + failedTasks.add(worker.task); + } + } + } + + @VisibleForTesting + void schedule(ParsingQueueTask.Builder taskBuilder) { + schedule(taskBuilder.build()); + } + + private void schedule(ParsingQueueTask task) { + EventParsingWorker worker = new EventParsingWorker(task); ScheduledFuture<?> future = pool.schedule(worker); if (future != null) { pending.put(worker, future); } } - protected void markAsCompleted(EventParsingWorker worker) { - pending.remove(worker); - } + class EventParsingWorker implements ProjectRunnable { + private final ParsingQueueTask task; + boolean running; + boolean successful = true; - abstract class EventParsingWorker implements ProjectRunnable { - private final String repoName; - private final String refName; - private final String commitId; - private final EiffelEventType type; - private boolean running; - - public EventParsingWorker(EiffelEventType type, String repoName, String refName) { - this(type, repoName, refName, null); + public EventParsingWorker(ParsingQueueTask task) { + this.task = task; } - public EventParsingWorker( - EiffelEventType type, String repoName, String refName, String commitId) { - this.type = type; - this.repoName = repoName; - this.refName = - refName.startsWith(RefNames.REFS_HEADS) - ? refName.substring(RefNames.REFS_HEADS.length()) - : refName; - this.commitId = commitId; + public void doRun() { + String append = null; + try { + switch (task.type) { + case SCC: + if (task.patchsetCreatedEvent != null) { + append = ", from event, "; + eventParser.createAndScheduleSccFromEvent(task.patchsetCreatedEvent); + } else if (task.commitId != null) { + eventParser.createAndScheduleSccFromCommit( + task.repoName, task.branchRefOrTag, task.commitId); + } else { + eventParser.createAndScheduleSccFromBranch(task.repoName, task.branchRefOrTag); + } + break; + case SCS: + if (task.commitId != null) { + append = ", from submit, "; + eventParser.createAndScheduleMissingScss( + SourceChangeEventKey.scsKey(task.repoName, task.branchRefOrTag, task.commitId), + task.previousTip, + task.updater, + task.updateTime); + } else { + eventParser.createAndScheduleMissingScssFromBranch( + task.repoName, task.branchRefOrTag); + } + break; + case ARTC: + eventParser.createAndScheduleArtc( + task.repoName, task.branchRefOrTag, task.updateTime, task.force); + break; + case CD: + default: + logger.atWarning().log("Cannot schedule events for type: %s.", task.type); + } + successful = true; + } catch (EventParsingException e) { + logger.atSevere().withCause(e).log( + "Failed to create events%sfrom %s", append != null ? append : " ", task); + successful = false; + } } - public abstract void doRun(); - @Override public void run() { running = true; @@ -230,7 +231,7 @@ @Override public NameKey getProjectNameKey() { - return Project.nameKey(repoName); + return Project.nameKey(task.repoName); } @Override @@ -245,14 +246,7 @@ @Override public String toString() { - if (commitId != null) { - return String.format( - "Parsing: %s[%s, %s, %s]%s", - type.name(), repoName, refName, commitId, running ? " (running)" : ""); - } - return String.format( - "Parsing from branch: %s[%s, %s]%s", - type.name(), repoName, refName, running ? " (running)" : ""); + return String.format("Parsing: %s%s", task, running ? " (running)" : ""); } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EventParsingException.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EventParsingException.java new file mode 100644 index 0000000..7503b63 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EventParsingException.java
@@ -0,0 +1,38 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +/** Thrown when EventParsing fails. */ +public class EventParsingException extends Exception { + + /** */ + private static final long serialVersionUID = 1L; + + /** + * @param cause - The cause of this exception. + * @param formatString - as if passed to String.format(formatString, objects) + * @param objects - as if passed to String.format(formatString, objects) + */ + public EventParsingException(Throwable cause, String formatString, Object... objects) { + super(String.format(formatString, objects), cause); + } + + /** + * @param formatString - as if passed to String.format(formatString, objects) + * @param objects - as if passed to String.format(formatString, objects) + */ + public EventParsingException(String formatString, Object... objects) { + super(String.format(formatString, objects)); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueuePersistence.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueuePersistence.java new file mode 100644 index 0000000..25874f0 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueuePersistence.java
@@ -0,0 +1,96 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.flogger.FluentLogger; +import com.google.common.reflect.TypeToken; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.server.config.SitePaths; +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Set; + +public class ParsingQueuePersistence { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final Gson GSON = new Gson(); + private static final String PERSISTED_QUEUE_FILE_NAME = "persisted-queue.json"; + private static final String PERSISTED_FAILED_FILE_NAME = "peristed-failed.json"; + + private static void persistTo(Set<ParsingQueueTask> tasks, Path dir, String fileName) { + Path file = dir.resolve(fileName); + try { + if (Files.isRegularFile(file)) { + Files.delete(file); + } + if (tasks == null || tasks.isEmpty()) { + return; + } + if (!Files.isDirectory(dir)) { + Files.createDirectory(dir); + } + Files.write(file, GSON.toJson(tasks).getBytes(UTF_8)); + } catch (IOException e) { + logger.atSevere().withCause(e).log("Failed to persist tasks to : %s", file); + } + } + + private static Set<ParsingQueueTask> getTasksFrom(Path file) { + Set<ParsingQueueTask> persistedTasks = null; + try { + if (Files.isRegularFile(file)) { + persistedTasks = + GSON.fromJson( + Files.newBufferedReader(file, UTF_8), + new TypeToken<Set<ParsingQueueTask>>() { + + private static final long serialVersionUID = 1L; + }.getType()); + } + } catch (JsonIOException | JsonSyntaxException | IOException e) { + logger.atSevere().withCause(e).log("Failed to get tasks from %s.", file); + } + return persistedTasks != null ? persistedTasks : Set.of(); + } + + private final Path pluginDataDir; + + @Inject + public ParsingQueuePersistence(SitePaths sitePaths, @PluginName String pluginName) { + this.pluginDataDir = sitePaths.data_dir.resolve(pluginName); + } + + public void persistQueue(Set<ParsingQueueTask> tasks) { + persistTo(tasks, pluginDataDir, PERSISTED_QUEUE_FILE_NAME); + } + + public void persistFailed(Set<ParsingQueueTask> failedTasks) { + persistTo(failedTasks, pluginDataDir, PERSISTED_FAILED_FILE_NAME); + } + + public Collection<ParsingQueueTask> getPersistedQueue() { + return getTasksFrom(pluginDataDir.resolve(PERSISTED_QUEUE_FILE_NAME)); + } + + public Collection<ParsingQueueTask> getFailedTasks() { + return getTasksFrom(pluginDataDir.resolve(PERSISTED_FAILED_FILE_NAME)); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTask.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTask.java new file mode 100644 index 0000000..245c4b3 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTask.java
@@ -0,0 +1,178 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import com.google.gerrit.entities.RefNames; +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.events.RevisionCreatedListener; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType; +import java.util.Objects; + +/** + * DTO that contains all necessary data to describe and reschedule a task in the {@link + * EiffelEventParsingQueue}. + */ +public class ParsingQueueTask { + static Builder builder(EiffelEventType type, String repoName, String branchRefOrTag) { + return new Builder(type, repoName, branchRefOrTag); + } + + /** + * The event contains all information necessary to create a SCC without querying the index. + * Setting the event also automatically sets repoName, branchRefOrTag, commitId from the event and + * type to SCC and ignores all other fields. + */ + static Builder sccBuilder(RevisionCreatedListener.Event event) { + return new Builder(event); + } + + public static class Builder { + private EiffelEventType _type; + private String _repoName; + private String _branchRefOrTag; + private String _commitId = null; + private AccountInfo _updater = null; + private Long _updateTime = null; + private Boolean _force = null; + private String _previousTip = null; + private RevisionCreatedListener.Event _patchsetCreatedEvent; + + private Builder(EiffelEventType type, String repoName, String branchRefOrTag) { + this._type = type; + this._repoName = repoName; + this._branchRefOrTag = branchRefOrTag; + } + + private Builder(RevisionCreatedListener.Event event) { + this._patchsetCreatedEvent = event; + } + + public Builder commit(String commitId) { + this._commitId = commitId; + return this; + } + + public Builder updater(AccountInfo updater) { + this._updater = updater; + return this; + } + + public Builder updateTime(Long updateTime) { + this._updateTime = updateTime; + return this; + } + + public Builder force(boolean force) { + this._force = force; + return this; + } + + public Builder previousTip(String previousTip) { + this._previousTip = previousTip; + return this; + } + + public ParsingQueueTask build() { + if (this._patchsetCreatedEvent != null) { + return new ParsingQueueTask(_patchsetCreatedEvent); + } + return new ParsingQueueTask( + this._type, + this._repoName, + this._branchRefOrTag, + this._commitId, + this._updater, + this._updateTime, + this._previousTip, + this._force); + } + } + + final String repoName; + final String branchRefOrTag; + final String commitId; + final EiffelEventType type; + final AccountInfo updater; + final Long updateTime; + final Boolean force; + final String previousTip; + final RevisionCreatedListener.Event patchsetCreatedEvent; + + private ParsingQueueTask( + EiffelEventType type, + String repoName, + String branchRefOrTag, + String commitId, + AccountInfo updater, + Long updateTime, + String previousTip, + Boolean force) { + this.type = type; + this.repoName = repoName; + this.branchRefOrTag = + branchRefOrTag.startsWith(RefNames.REFS_TAGS) + ? branchRefOrTag.substring(RefNames.REFS_TAGS.length()) + : branchRefOrTag; + this.commitId = commitId; + this.updater = updater; + this.updateTime = updateTime; + this.previousTip = previousTip; + this.force = force; + this.patchsetCreatedEvent = null; + } + + private ParsingQueueTask(RevisionCreatedListener.Event patchsetCreatedEvent) { + this.type = EiffelEventType.SCC; + this.repoName = patchsetCreatedEvent.getChange().project; + this.branchRefOrTag = patchsetCreatedEvent.getChange().branch; + this.commitId = patchsetCreatedEvent.getRevision().commit.commit; + this.updater = null; + this.updateTime = null; + this.previousTip = null; + this.force = null; + this.patchsetCreatedEvent = patchsetCreatedEvent; + } + + @Override + public String toString() { + String target = + branchRefOrTag.startsWith(RefNames.REFS_HEADS) + ? branchRefOrTag.substring(RefNames.REFS_HEADS.length()) + : branchRefOrTag; + if (commitId != null) { + return String.format("%s[%s, %s, %s]", type.name(), repoName, target, commitId); + } + return String.format("%s[%s, %s]", type.name(), repoName, target); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ParsingQueueTask) { + ParsingQueueTask that = (ParsingQueueTask) o; + return Objects.equals(this.type, that.type) + && Objects.equals(this.repoName, that.repoName) + && Objects.equals(this.commitId, that.commitId) + && Objects.equals(this.updater, that.updater) + && Objects.equals(this.updateTime, that.updateTime) + && Objects.equals(this.previousTip, that.previousTip) + && Objects.equals(this.force, that.force); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(type, repoName, commitId, updater, updateTime, previousTip, force); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateSccs.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateSccs.java index 1f5deb6..1f4416e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateSccs.java +++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/CreateSccs.java
@@ -60,7 +60,7 @@ .findFirst() .map(c -> c.change().getDest().branch()); if (targetBranch.isPresent()) { - queue.scheduleSccCreation(resource.getName(), resource.getRef(), targetBranch.get()); + queue.scheduleSccCreation(resource.getName(), targetBranch.get(), resource.getRevision()); return EventCreationResponse.scc(resource, targetBranch.get()); } throw new BadRequestException(
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 3652427..0433b7a 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java
@@ -29,6 +29,7 @@ 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.EiffelEventType; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkInfo; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType; import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory; @@ -117,6 +118,22 @@ result.getCommit().getName()); } + protected void setScsHandled() throws Exception { + markAsHandled(eventForHead(EiffelEventType.SCS), getHead(repo(), "HEAD")); + } + + protected SourceChangeEventKey eventForHead(EiffelEventType type) throws Exception { + return SourceChangeEventKey.create(project.get(), getHead(), getHeadRevision(), type); + } + + protected String getHead() throws Exception { + return gApi.projects().name(project.get()).head(); + } + + protected String getHeadRevision() throws Exception { + return getHead(repo(), "HEAD").getName(); + } + protected UUID markAsHandled(EventKey key) throws EventStorageException { Optional<UUID> eventId = TestEventStorage.INSTANCE.getEventId(key); if (eventId.isPresent()) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java index f51402a..345753d 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java
@@ -27,6 +27,8 @@ import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorage; import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser; +import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserImpl; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingExecutor; import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueue; import org.junit.Ignore; @@ -46,6 +48,7 @@ bind(EiffelEventHub.Consumer.class).to(TestEventPublisher.class); bind(EventsFilter.class).toProvider(TestEventsFilterProvider.class); + bind(EiffelEventParser.class).to(EiffelEventParserImpl.class); bind(EiffelEventParsingQueue.class); bind(EiffelEventFactory.class).toProvider(TestEventFactoryProvider.class);
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 f578ad3..e11ee3d 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
@@ -14,6 +14,8 @@ package com.googlesource.gerrit.plugins.eventseiffel.parsing; +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.testing.GerritJUnit.assertThrows; import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCS; import static org.junit.Assert.assertEquals; @@ -24,28 +26,12 @@ import com.google.gerrit.acceptance.config.GlobalPluginConfig; import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.extensions.common.AccountInfo; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; -import com.google.inject.Singleton; -import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; -import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest; import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub; import com.googlesource.gerrit.plugins.eventseiffel.TestEventStorage; -import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdCacheImpl; -import com.googlesource.gerrit.plugins.eventseiffel.config.EiffelConfig; -import com.googlesource.gerrit.plugins.eventseiffel.config.EventIdCacheConfig; -import com.googlesource.gerrit.plugins.eventseiffel.config.EventMappingConfig; -import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.ArtifactEventKey; 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.EventStorage; import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelSourceChangeSubmittedEventInfo; -import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory; -import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.time.Instant; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.revwalk.RevCommit; @@ -54,16 +40,14 @@ @TestPlugin( name = "events-eiffel", - sysModule = - "com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParserIT$TestModule") -public class EiffelEventParserIT extends EiffelEventsTest { - private static final Long EPOCH_MILLIS = 978307261000l; + sysModule = "com.googlesource.gerrit.plugins.eventseiffel.parsing.ParsingTestModule") +public class EiffelEventParserIT extends EiffelEventParsingTest { - private EiffelEventParser eventParser; + private EiffelEventParserImpl eventParser; @Before public void setUp() { - eventParser = plugin.getSysInjector().getInstance(EiffelEventParser.class); + eventParser = plugin.getSysInjector().getInstance(EiffelEventParserImpl.class); TestEventHub.EVENTS.clear(); TestEventsFilterProvider.reset(); } @@ -242,12 +226,6 @@ 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 { String ref = @@ -273,9 +251,11 @@ TestEventsFilterProvider.setBlockedRefPatterns("refs/heads/master"); String ref = createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length()); - - eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false); - assertEquals(0, TestEventHub.EVENTS.size()); + EventParsingException thrown = + assertThrows( + EventParsingException.class, + () -> eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false)); + assertThat(thrown).hasMessageThat().startsWith("Could not find any unblocked branch for SCS"); } @Test @@ -302,17 +282,20 @@ @Test public void artcQueuedNoTagCreated() throws Exception { - eventParser.createAndScheduleArtc(project.get(), TAG_NAME, EPOCH_MILLIS, false); - assertEquals(0, TestEventHub.EVENTS.size()); + EventParsingException thrown = + assertThrows( + EventParsingException.class, + () -> eventParser.createAndScheduleArtc(project.get(), TAG_NAME, EPOCH_MILLIS, false)); + assertThat(thrown).hasMessageThat().startsWith("Cannot find tag"); } @Test - public void annotatedTagArtcQueuedscsHandled() throws Exception { + public void annotatedTagArtcQueuedScsHandled() throws Exception { assertArtcQueuedScsHandled(true); } @Test - public void lightWeightTagArtcQueuedscsHandled() throws Exception { + public void LightweightTagArtcQueuedScsHandled() throws Exception { assertArtcQueuedScsHandled(false); } @@ -334,7 +317,7 @@ } @Test - public void artcQueuedcdHandled() throws Exception { + public void artcQueuedCdHandled() throws Exception { String ref = setCdHandled(); eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false); assertEquals(0, TestEventHub.EVENTS.size()); @@ -369,57 +352,4 @@ private void assertNbrQueriesFor(SourceChangeEventKey key, int nbrQueries) { assertEquals(nbrQueries, TestEventStorage.INSTANCE.queriesFor(key)); } - - private String getHead() throws Exception { - return gApi.projects().name(project.get()).head(); - } - - private String getHeadRevision() throws Exception { - return getHead(repo(), "HEAD").getName(); - } - - private void assertCorrectEvent(int order, EventKey expected) { - EventKey actual = EventKey.fromEvent(TestEventHub.EVENTS.get(order)); - assertEquals(expected, actual); - } - - protected static String tagCompositionName(String projectName) { - return tagCompositionName(projectName, "localhost"); - } - - protected static String tagCompositionName(String projectName, String namespace) { - return "scmtag/git/" + namespace + "/" + URLEncoder.encode(projectName, StandardCharsets.UTF_8); - } - - protected static String tagPURL(String projectName, String tagName) { - return tagPURL(projectName, tagName, "localhost"); - } - - protected static String tagPURL(String projectName, String tagName, String namespace) { - return String.format( - TAG_PURL_TEMPLATE, - namespace, - projectName, - tagName, - String.format("ssh://%s/%s", HOST_NAME, projectName), - tagName); - } - - public static class TestModule extends AbstractModule { - @Override - protected void configure() { - bind(EventStorage.class).toProvider(TestEventStorage.Provider.class).in(Singleton.class); - bind(EventIdCacheConfig.class).toProvider(EventIdCacheConfig.Provider.class); - bind(EventsFilter.class).toProvider(TestEventsFilterProvider.class); - install(EiffelEventIdCacheImpl.module()); - bind(EiffelEventHub.class).to(TestEventHub.class); - bind(EventMappingConfig.class).toProvider(EventMappingConfig.Provider.class); - bind(EiffelEventMapper.class); - bind(EiffelEventParser.class); - bind(EiffelConfig.class).toProvider(EiffelConfig.Provider.class).in(Scopes.SINGLETON); - bind(EiffelEventFactory.class) - .toProvider(EiffelEventFactory.Provider.class) - .in(Scopes.SINGLETON); - } - } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueueIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueueIT.java new file mode 100644 index 0000000..3de49b8 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingQueueIT.java
@@ -0,0 +1,148 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static org.junit.Assert.assertEquals; + +import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.acceptance.TestPlugin; +import com.google.gerrit.acceptance.UseLocalDisk; +import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.ArtifactEventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.CompositionDefinedEventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType; +import java.util.Set; +import org.eclipse.jgit.lib.Constants; +import org.junit.Before; +import org.junit.Test; + +@UseLocalDisk +@TestPlugin( + name = "events-eiffel", + sysModule = + "com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueueIT$TestModule") +public class EiffelEventParsingQueueIT extends EiffelEventParsingTest { + + private ParsingQueuePersistence queuePersistence; + private EiffelEventParsingQueue parsingQueue; + + @Before + public void setUp() throws Exception { + queuePersistence = plugin.getSysInjector().getInstance(ParsingQueuePersistence.class); + parsingQueue = plugin.getSysInjector().getInstance(EiffelEventParsingQueue.class); + TestEventHub.EVENTS.clear(); + } + + @Test + public void persistedScsFromBranchIsScheduledAndPushed() throws Exception { + queuePersistence.persistQueue( + Set.of(ParsingQueueTask.builder(EiffelEventType.SCS, project.get(), getHead()).build())); + parsingQueue.init(); + assertCorrectEvent(0, eventForHead(EiffelEventType.SCC)); + assertCorrectEvent(1, eventForHead(EiffelEventType.SCS)); + } + + @Test + public void persistedScsFromCommitIsScheduledAndPushed() throws Exception { + setScsHandled(); + PushOneCommit.Result res = createChange(); + merge(res); + queuePersistence.persistQueue( + Set.of( + ParsingQueueTask.builder(EiffelEventType.SCS, project.get(), "refs/heads/master") + .commit(res.getCommit().name()) + .build())); + parsingQueue.init(); + assertCorrectEvent(0, toSccKey(res)); + assertCorrectEvent(1, toScsKey(res)); + } + + @Test + public void persistedSccFromCommitIsScheduledAndPushed() throws Exception { + setScsHandled(); + PushOneCommit.Result res = createChange(); + queuePersistence.persistQueue( + Set.of( + ParsingQueueTask.builder(EiffelEventType.SCC, project.get(), "refs/heads/master") + .commit(res.getCommit().name()) + .build())); + parsingQueue.init(); + assertCorrectEvent(0, toSccKey(res)); + } + + @Test + public void persistedSccFromBranchIsScheduledAndPushed() throws Exception { + setScsHandled(); + PushOneCommit.Result res = createChange(); + merge(res); + queuePersistence.persistQueue( + Set.of( + ParsingQueueTask.builder(EiffelEventType.SCS, project.get(), "refs/heads/master") + .build())); + parsingQueue.init(); + assertCorrectEvent(0, toSccKey(res)); + } + + @Test + public void persistedArtcIsScheduledAndPushed() throws Exception { + setScsHandled(); + String tag = + createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length()); + ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), tag, "localhost")); + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(tagCompositionName(project.get(), "localhost"), tag); + queuePersistence.persistQueue( + Set.of( + ParsingQueueTask.builder(EiffelEventType.ARTC, project.get(), tag) + .updateTime(EPOCH_MILLIS) + .force(false) + .build())); + parsingQueue.init(); + assertEquals(2, TestEventHub.EVENTS.size()); + assertCorrectEvent(0, cd); + assertCorrectEvent(1, artc); + } + + @Test + public void failedTaskIsRescheduled() throws Exception { + setScsHandled(); + String tagName = "a-tag"; + ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), tagName, "localhost")); + CompositionDefinedEventKey cd = + CompositionDefinedEventKey.create(tagCompositionName(project.get(), "localhost"), tagName); + + /* Scheduling ArtC creation for a non-existing tag fails. */ + parsingQueue.schedule( + ParsingQueueTask.builder(EiffelEventType.ARTC, project.get(), tagName) + .updateTime(EPOCH_MILLIS) + .force(false)); + assertEquals(0, TestEventHub.EVENTS.size()); + + /* Creating the tag should mean that rescheduling the failed task should work. */ + createTagRef(tagName, getHead(repo(), "HEAD").getName(), true); + parsingQueue.rescheduleFailed(); + assertEquals(2, TestEventHub.EVENTS.size()); + assertCorrectEvent(0, cd); + assertCorrectEvent(1, artc); + } + + public static class TestModule extends ParsingTestModule { + + @Override + protected void configure() { + bind(EiffelEventParsingExecutor.class).to(EiffelEventParsingExecutor.Direct.class); + super.configure(); + } + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java new file mode 100644 index 0000000..a606e14 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java
@@ -0,0 +1,42 @@ +package com.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static org.junit.Assert.assertEquals; + +import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest; +import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import org.junit.Ignore; + +@Ignore +public class EiffelEventParsingTest extends EiffelEventsTest { + protected static final Long EPOCH_MILLIS = 978307261000l; + + protected static String tagCompositionName(String projectName) { + return tagCompositionName(projectName, "localhost"); + } + + protected static String tagCompositionName(String projectName, String namespace) { + return "scmtag/git/" + namespace + "/" + URLEncoder.encode(projectName, StandardCharsets.UTF_8); + } + + protected static String tagPURL(String projectName, String tagName) { + return tagPURL(projectName, tagName, "localhost"); + } + + protected static String tagPURL(String projectName, String tagName, String namespace) { + return String.format( + TAG_PURL_TEMPLATE, + namespace, + projectName, + tagName, + String.format("ssh://%s/%s", HOST_NAME, projectName), + tagName); + } + + protected void assertCorrectEvent(int order, EventKey expected) { + EventKey actual = EventKey.fromEvent(TestEventHub.EVENTS.get(order)); + assertEquals(expected, actual); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueuePersistenceIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueuePersistenceIT.java new file mode 100644 index 0000000..0923634 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueuePersistenceIT.java
@@ -0,0 +1,224 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.Lists; +import com.google.gerrit.acceptance.LightweightPluginDaemonTest; +import com.google.gerrit.acceptance.TestPlugin; +import com.google.gerrit.acceptance.UseLocalDisk; +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.events.RevisionCreatedListener.Event; +import com.google.gerrit.server.git.ProjectRunnable; +import com.google.inject.AbstractModule; +import com.google.inject.Provider; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.Before; +import org.junit.Test; + +@UseLocalDisk +@TestPlugin( + name = "events-eiffel", + sysModule = + "com.googlesource.gerrit.plugins.eventseiffel.parsing.ParsingQueuePersistenceIT$TestModule") +public class ParsingQueuePersistenceIT extends LightweightPluginDaemonTest { + + private ParsingQueuePersistence queuePersistence; + private EiffelEventParsingQueue parsingQueue; + + @Before + public void setUp() throws Exception { + queuePersistence = plugin.getSysInjector().getInstance(ParsingQueuePersistence.class); + /* Purge persistence from previous runs. */ + queuePersistence.persistFailed(Set.of()); + queuePersistence.persistQueue(Set.of()); + + parsingQueue = plugin.getSysInjector().getInstance(EiffelEventParsingQueue.class); + } + + @Test + public void queueIsPersisted() throws Exception { + Set<ParsingQueueTask> original = createTasks(1, 5); + queuePersistence.persistQueue(original); + assertThat(original).containsExactlyElementsIn(queuePersistence.getPersistedQueue()); + } + + @Test + public void failedTasksArePersisted() throws Exception { + Set<ParsingQueueTask> original = createTasks(1, 5); + queuePersistence.persistFailed(original); + assertThat(original).containsExactlyElementsIn(queuePersistence.getFailedTasks()); + } + + @Test + public void multipleQueuePersistenceRuns() throws Exception { + Set<ParsingQueueTask> original = createTasks(1, 6); + queuePersistence.persistQueue(original); + assertThat(original).containsExactlyElementsIn(queuePersistence.getPersistedQueue()); + + original = createTasks(6, 10); + queuePersistence.persistQueue(original); + assertThat(original).containsExactlyElementsIn(queuePersistence.getPersistedQueue()); + } + + @Test + public void persistedQueuedTasksAreScheduledOnInit() throws Exception { + Set<ParsingQueueTask> tasks = createTasks(1, 6); + queuePersistence.persistQueue(tasks); + parsingQueue.init(); + assertThat( + TestExecutor.scheduledItems().stream() + .map(p -> p.getProjectNameKey()) + .collect(Collectors.toList())) + .containsExactlyElementsIn( + tasks.stream().map(t -> Project.nameKey(t.repoName)).collect(Collectors.toList())); + } + + @Test + public void persistedFailedTasksAreScheduledOnInit() throws Exception { + Set<ParsingQueueTask> tasks = createTasks(1, 6); + queuePersistence.persistFailed(tasks); + parsingQueue.init(); + assertThat( + TestExecutor.scheduledItems().stream() + .map(p -> p.getProjectNameKey()) + .collect(Collectors.toList())) + .containsExactlyElementsIn( + tasks.stream().map(t -> Project.nameKey(t.repoName)).collect(Collectors.toList())); + } + + private Set<ParsingQueueTask> createTasks(int start, int end) { + return IntStream.range(start, end) + .mapToObj(ParsingQueuePersistenceIT::createTask) + .collect(Collectors.toSet()); + } + + public static ParsingQueueTask createTask(int id) { + return ParsingQueueTask.builder(EiffelEventType.SCC, "repo" + id, "branch" + id) + .commit("commit-sha1" + id) + .build(); + } + + public static class TestExecutor + implements EiffelEventParsingExecutor, Provider<EiffelEventParsingExecutor> { + private static TestExecutor INSTANCE; + + public static List<ProjectRunnable> scheduledItems() { + return INSTANCE.scheduledItems; + } + + private final List<ProjectRunnable> scheduledItems = Lists.newArrayList(); + + @Override + public ScheduledFuture<?> schedule(ProjectRunnable runnable) { + scheduledItems.add(runnable); + return new ScheduledFuture<ProjectRunnable>() { + + @Override + public long getDelay(TimeUnit arg0) { + return 0; + } + + @Override + public int compareTo(Delayed arg0) { + return 0; + } + + @Override + public boolean cancel(boolean arg0) { + return false; + } + + @Override + public ProjectRunnable get() throws InterruptedException, ExecutionException { + return null; + } + + @Override + public ProjectRunnable get(long arg0, TimeUnit arg1) + throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return false; + } + }; + } + + @Override + public void shutDown() {} + + @Override + public EiffelEventParsingExecutor get() { + INSTANCE = new TestExecutor(); + return INSTANCE; + } + } + + public static class NoOpEventParser implements EiffelEventParser { + + public NoOpEventParser() {} + + @Override + public void createAndScheduleSccFromEvent(Event event) {} + + @Override + public void createAndScheduleSccFromBranch(String repoName, String branchRef) {} + + @Override + public void createAndScheduleSccFromCommit(String repoName, String branchRef, String commit) {} + + @Override + public void createAndScheduleMissingScssFromBranch(String repoName, String branchRef) {} + + @Override + public void createAndScheduleMissingScss( + SourceChangeEventKey scs, + String commitSha1TransactionEnd, + AccountInfo submitter, + Long submittedAt) {} + + @Override + public void createAndScheduleArtc( + String repoName, String tagName, Long creationTime, boolean force) {} + } + + public static class TestModule extends AbstractModule { + @Override + protected void configure() { + bind(ParsingQueuePersistence.class); + bind(EiffelEventParser.class).to(NoOpEventParser.class); + bind(EiffelEventParsingExecutor.class).toProvider(TestExecutor.class); + } + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTaskTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTaskTest.java new file mode 100644 index 0000000..6c85763 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingQueueTaskTest.java
@@ -0,0 +1,69 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.eventseiffel.parsing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gson.Gson; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType; +import org.junit.Test; + +public class ParsingQueueTaskTest { + + @Test + public void testParsingQueueTaskSerializeable() { + AccountInfo accountInfo = new AccountInfo(1); + ParsingQueueTask task = + ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch") + .commit("commit-sha1") + .updater(accountInfo) + .updateTime(0L) + .previousTip("previous") + .force(true) + .build(); + String json = new Gson().toJson(task); + String expected = + "{\"repoName\":\"repo\",\"branchRefOrTag\":\"branch\",\"commitId\":\"commit-sha1\",\"type\":\"EiffelSourceChangeCreatedEvent\",\"updater\":{\"_accountId\":1},\"updateTime\":0,\"force\":true,\"previousTip\":\"previous\"}"; + assertEquals(json, expected); + } + + @Test + public void testParsingQueueTaskDeserializeable() { + String json = + "{\"repoName\":\"repo\",\"branchRefOrTag\":\"branch\",\"commitId\":\"commit-sha1\",\"type\":\"EiffelSourceChangeCreatedEvent\",\"updater\":{\"_accountId\":1},\"updateTime\":0,\"force\":true,\"previousTip\":\"previous\"}"; + ParsingQueueTask deSerialized = new Gson().fromJson(json, ParsingQueueTask.class); + assertEquals(deSerialized.repoName, "repo"); + assertEquals(deSerialized.branchRefOrTag, "branch"); + assertEquals(deSerialized.commitId, "commit-sha1"); + assertEquals(deSerialized.type, EiffelEventType.SCC); + assertEquals(deSerialized.updater._accountId, Integer.valueOf(1)); + assertEquals(deSerialized.updateTime, Long.valueOf(0)); + assertEquals(deSerialized.force, Boolean.TRUE); + assertEquals(deSerialized.previousTip, "previous"); + } + + @Test + public void testEqualsAndHashCode() { + ParsingQueueTask dis = ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch").build(); + ParsingQueueTask dat = ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch").build(); + assertEquals(dis, dat); + assertEquals(dis.hashCode(), dat.hashCode()); + ParsingQueueTask theOther = + ParsingQueueTask.builder(EiffelEventType.SCC, "repo", "branch").commit("commit").build(); + assertNotEquals(dis, theOther); + assertNotEquals(dis.hashCode(), theOther.hashCode()); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java new file mode 100644 index 0000000..b01640d --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java
@@ -0,0 +1,37 @@ +package com.googlesource.gerrit.plugins.eventseiffel.parsing; + +import com.google.inject.AbstractModule; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; +import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest.TestEventsFilterProvider; +import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub; +import com.googlesource.gerrit.plugins.eventseiffel.TestEventStorage; +import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdCacheImpl; +import com.googlesource.gerrit.plugins.eventseiffel.config.EiffelConfig; +import com.googlesource.gerrit.plugins.eventseiffel.config.EventIdCacheConfig; +import com.googlesource.gerrit.plugins.eventseiffel.config.EventMappingConfig; +import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter; +import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorage; +import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory; +import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper; +import org.junit.Ignore; + +@Ignore +public class ParsingTestModule extends AbstractModule { + @Override + protected void configure() { + bind(EventStorage.class).toProvider(TestEventStorage.Provider.class).in(Singleton.class); + bind(EventIdCacheConfig.class).toProvider(EventIdCacheConfig.Provider.class); + bind(EventsFilter.class).toProvider(TestEventsFilterProvider.class); + install(EiffelEventIdCacheImpl.module()); + bind(EiffelEventHub.class).to(TestEventHub.class); + bind(EventMappingConfig.class).toProvider(EventMappingConfig.Provider.class); + bind(EiffelEventMapper.class); + bind(EiffelEventParser.class).to(EiffelEventParserImpl.class); + bind(EiffelConfig.class).toProvider(EiffelConfig.Provider.class).in(Scopes.SINGLETON); + bind(EiffelEventFactory.class) + .toProvider(EiffelEventFactory.Provider.class) + .in(Scopes.SINGLETON); + } +}