Merge branch 'stable-3.0'

* stable-3.0:
  Clean up ReplicationQueue tests
  Convert ReferenceUpdateEvent class to AutoValue
  Fix issue with dropping events on start

Change-Id: I5a4bc84a829600865fdd551152219cd1bbe5aa91
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
index 5bc7320..3e17167 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -14,7 +14,9 @@
 
 package com.googlesource.gerrit.plugins.replication;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Queues;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.HeadUpdatedListener;
 import com.google.gerrit.extensions.events.LifecycleListener;
@@ -30,6 +32,7 @@
 import com.googlesource.gerrit.plugins.replication.ReplicationTasksStorage.ReplicateRefUpdate;
 import java.util.HashSet;
 import java.util.Optional;
+import java.util.Queue;
 import java.util.Set;
 import org.eclipse.jgit.transport.URIish;
 import org.slf4j.Logger;
@@ -52,6 +55,7 @@
   private final ReplicationTasksStorage replicationTasksStorage;
   private volatile boolean running;
   private volatile boolean replaying;
+  private final Queue<ReferenceUpdatedEvent> beforeStartupEventsQueue;
 
   @Inject
   ReplicationQueue(
@@ -65,6 +69,7 @@
     destinations = rd;
     stateLog = sl;
     replicationTasksStorage = rts;
+    beforeStartupEventsQueue = Queues.newConcurrentLinkedQueue();
   }
 
   @Override
@@ -73,6 +78,7 @@
       destinations.get().startup(workQueue);
       running = true;
       firePendingEvents();
+      fireBeforeStartupEvents();
     }
   }
 
@@ -125,7 +131,10 @@
   private void onGitReferenceUpdated(String projectName, String refName) {
     ReplicationState state = new ReplicationState(new GitUpdateProcessing(dispatcher.get()));
     if (!running) {
-      stateLog.warn("Replication plugin did not finish startup before event", state);
+      stateLog.warn(
+          "Replication plugin did not finish startup before event, event replication is postponed",
+          state);
+      beforeStartupEventsQueue.add(ReferenceUpdatedEvent.create(projectName, refName));
       return;
     }
 
@@ -173,4 +182,28 @@
     destinations.get().getURIs(Optional.empty(), p, FilterType.ALL).entries().stream()
         .forEach(e -> e.getKey().scheduleUpdateHead(e.getValue(), p, event.getNewHeadName()));
   }
+
+  private void fireBeforeStartupEvents() {
+    Set<String> eventsReplayed = new HashSet<>();
+    for (ReferenceUpdatedEvent event : beforeStartupEventsQueue) {
+      String eventKey = String.format("%s:%s", event.projectName(), event.refName());
+      if (!eventsReplayed.contains(eventKey)) {
+        repLog.info("Firing pending task {}", event);
+        onGitReferenceUpdated(event.projectName(), event.refName());
+        eventsReplayed.add(eventKey);
+      }
+    }
+  }
+
+  @AutoValue
+  abstract static class ReferenceUpdatedEvent {
+
+    static ReferenceUpdatedEvent create(String projectName, String refName) {
+      return new AutoValue_ReplicationQueue_ReferenceUpdatedEvent(projectName, refName);
+    }
+
+    public abstract String projectName();
+
+    public abstract String refName();
+  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
index d39cf79..eed5158 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
@@ -306,8 +306,26 @@
     }
   }
 
-  private Project.NameKey createTestProject(String name) throws Exception {
-    return projectOperations.newProject().name(name).create();
+  @Test
+  public void shouldNotDropEventsWhenStarting() throws Exception {
+    Project.NameKey targetProject = createTestProject(project + "replica");
+
+    setReplicationDestination("foo", "replica", ALL_PROJECTS);
+    reloadConfig();
+
+    replicationQueueStop();
+    Result pushResult = createChange();
+    replicationQueueStart();
+
+    RevCommit sourceCommit = pushResult.getCommit();
+    String sourceRef = pushResult.getPatchSet().refName();
+
+    try (Repository repo = repoManager.openRepository(targetProject)) {
+      waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
+      Ref targetBranchRef = getRef(repo, sourceRef);
+      assertThat(targetBranchRef).isNotNull();
+      assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId());
+    }
   }
 
   private Ref getRef(Repository repo, String branchName) throws IOException {
@@ -348,11 +366,35 @@
   }
 
   private void reloadConfig() {
-    plugin.getSysInjector().getInstance(AutoReloadConfigDecorator.class).reload();
+    getAutoReloadConfigDecoratorInstance().reload();
   }
 
   private void shutdownDestinations() {
-    plugin.getSysInjector().getInstance(DestinationsCollection.class).shutdown();
+    getInstance(DestinationsCollection.class).shutdown();
+  }
+
+  private void replicationQueueStart() {
+    getReplicationQueueInstance().start();
+  }
+
+  private void replicationQueueStop() {
+    getReplicationQueueInstance().stop();
+  }
+
+  private AutoReloadConfigDecorator getAutoReloadConfigDecoratorInstance() {
+    return getInstance(AutoReloadConfigDecorator.class);
+  }
+
+  private ReplicationQueue getReplicationQueueInstance() {
+    return getInstance(ReplicationQueue.class);
+  }
+
+  private <T> T getInstance(Class<T> classObj) {
+    return plugin.getSysInjector().getInstance(classObj);
+  }
+
+  private Project.NameKey createTestProject(String name) throws Exception {
+    return projectOperations.newProject().name(name).create();
   }
 
   private List<ReplicateRefUpdate> listReplicationTasks(String refRegex) {