Introduce replication integration-test Similarly to the replication plugin, define an integration test that verifies the pull-replication functionality end-to-end. Change-Id: I93661993c078746fa5b808b2a19b941fdd4bdba6
diff --git a/BUILD b/BUILD index 7fa956c..705c332 100644 --- a/BUILD +++ b/BUILD
@@ -20,11 +20,30 @@ junit_tests( name = "pull_replication_tests", - srcs = glob(["src/test/java/**/*Test.java"]), + srcs = glob([ + "src/test/java/**/*Test.java", + "src/test/java/**/*IT.java", + ]), tags = ["pull-replication"], visibility = ["//visibility:public"], deps = PLUGIN_TEST_DEPS + PLUGIN_DEPS + [ ":pull-replication__plugin", + ":pull_replication_util", "//plugins/replication:replication", ], ) + +java_library( + name = "pull_replication_util", + testonly = True, + srcs = glob( + ["src/test/java/**/*.java"], + exclude = [ + "src/test/java/**/*Test.java", + "src/test/java/**/*IT.java", + ], + ), + deps = PLUGIN_TEST_DEPS + PLUGIN_DEPS + [ + ":pull-replication__plugin", + ], +)
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FakeGitReferenceUpdatedEvent.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FakeGitReferenceUpdatedEvent.java new file mode 100644 index 0000000..43331dc --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FakeGitReferenceUpdatedEvent.java
@@ -0,0 +1,94 @@ +// Copyright (C) 2020 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.replication.pull; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.api.changes.NotifyHandling; +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; +import org.eclipse.jgit.transport.ReceiveCommand; + +public class FakeGitReferenceUpdatedEvent implements GitReferenceUpdatedListener.Event { + private final String projectName; + private final String ref; + private final String oldObjectId; + private final String newObjectId; + private final ReceiveCommand.Type type; + + FakeGitReferenceUpdatedEvent( + Project.NameKey project, + String ref, + String oldObjectId, + String newObjectId, + ReceiveCommand.Type type) { + this.projectName = project.get(); + this.ref = ref; + this.oldObjectId = oldObjectId; + this.newObjectId = newObjectId; + this.type = type; + } + + @Override + public String getProjectName() { + return projectName; + } + + @Override + public String getRefName() { + return ref; + } + + @Override + public String getOldObjectId() { + return oldObjectId; + } + + @Override + public String getNewObjectId() { + return newObjectId; + } + + @Override + public boolean isCreate() { + return type == ReceiveCommand.Type.CREATE; + } + + @Override + public boolean isDelete() { + return type == ReceiveCommand.Type.DELETE; + } + + @Override + public boolean isNonFastForward() { + return type == ReceiveCommand.Type.UPDATE_NONFASTFORWARD; + } + + @Override + public AccountInfo getUpdater() { + return null; + } + + @Override + public String toString() { + return String.format( + "%s[%s,%s: %s -> %s]", + getClass().getSimpleName(), projectName, ref, oldObjectId, newObjectId); + } + + @Override + public NotifyHandling getNotify() { + return NotifyHandling.ALL; + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java new file mode 100644 index 0000000..53f7767 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java
@@ -0,0 +1,212 @@ +// Copyright (C) 2020 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.replication.pull; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.stream.Collectors.toList; + +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.acceptance.LightweightPluginDaemonTest; +import com.google.gerrit.acceptance.PushOneCommit.Result; +import com.google.gerrit.acceptance.SkipProjectClone; +import com.google.gerrit.acceptance.TestPlugin; +import com.google.gerrit.acceptance.UseLocalDisk; +import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.api.projects.BranchInput; +import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import org.eclipse.jgit.errors.ConfigInvalidException; +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.storage.file.FileBasedConfig; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.util.FS; +import org.junit.Test; + +@SkipProjectClone +@UseLocalDisk +@TestPlugin( + name = "pull-replication", + sysModule = "com.googlesource.gerrit.plugins.replication.pull.PullReplicationModule") +public class PullReplicationIT extends LightweightPluginDaemonTest { + private static final Optional<String> ALL_PROJECTS = Optional.empty(); + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final int TEST_REPLICATION_DELAY = 60; + private static final Duration TEST_TIMEOUT = Duration.ofSeconds(TEST_REPLICATION_DELAY * 2); + private static final String TEST_REPLICATION_SUFFIX = "suffix1"; + private static final String TEST_REPLICATION_REMOTE = "remote1"; + + @Inject private SitePaths sitePaths; + @Inject private ProjectOperations projectOperations; + private Path gitPath; + private FileBasedConfig config; + private FileBasedConfig secureConfig; + + @Override + public void setUpTestPlugin() throws Exception { + gitPath = sitePaths.site_path.resolve("git"); + + config = + new FileBasedConfig(sitePaths.etc_dir.resolve("replication.config").toFile(), FS.DETECTED); + setReplicationSource( + TEST_REPLICATION_REMOTE, + TEST_REPLICATION_SUFFIX, + ALL_PROJECTS); // Simulates a full replication.config initialization + config.save(); + + secureConfig = + new FileBasedConfig(sitePaths.etc_dir.resolve("secure.config").toFile(), FS.DETECTED); + setReplicationCredentials(TEST_REPLICATION_REMOTE, admin.username(), admin.httpPassword()); + secureConfig.save(); + + super.setUpTestPlugin(); + } + + @Test + public void shouldReplicateNewChangeRef() throws Exception { + testRepo = cloneProject(createTestProject(project + TEST_REPLICATION_SUFFIX)); + + Result pushResult = createChange(); + RevCommit sourceCommit = pushResult.getCommit(); + String sourceRef = pushResult.getPatchSet().refName(); + + ReplicationQueue pullReplicationQueue = getInstance(ReplicationQueue.class); + GitReferenceUpdatedListener.Event event = + new FakeGitReferenceUpdatedEvent( + project, + sourceRef, + ObjectId.zeroId().getName(), + sourceCommit.getId().getName(), + ReceiveCommand.Type.CREATE); + pullReplicationQueue.onGitReferenceUpdated(event); + + try (Repository repo = repoManager.openRepository(project)) { + waitUntil(() -> checkedGetRef(repo, sourceRef) != null); + + Ref targetBranchRef = getRef(repo, sourceRef); + assertThat(targetBranchRef).isNotNull(); + assertThat(targetBranchRef.getObjectId()).isEqualTo(sourceCommit.getId()); + } + } + + @Test + public void shouldReplicateNewBranch() throws Exception { + String testProjectName = project + TEST_REPLICATION_SUFFIX; + createTestProject(testProjectName); + + String newBranch = "refs/heads/mybranch"; + String master = "refs/heads/master"; + BranchInput input = new BranchInput(); + input.revision = master; + gApi.projects().name(testProjectName).branch(newBranch).create(input); + String branchRevision = gApi.projects().name(testProjectName).branch(newBranch).get().revision; + + ReplicationQueue pullReplicationQueue = + plugin.getSysInjector().getInstance(ReplicationQueue.class); + GitReferenceUpdatedListener.Event event = + new FakeGitReferenceUpdatedEvent( + project, + newBranch, + ObjectId.zeroId().getName(), + branchRevision, + ReceiveCommand.Type.CREATE); + pullReplicationQueue.onGitReferenceUpdated(event); + + try (Repository repo = repoManager.openRepository(project); + Repository sourceRepo = repoManager.openRepository(project)) { + waitUntil(() -> checkedGetRef(repo, newBranch) != null); + + Ref targetBranchRef = getRef(repo, newBranch); + assertThat(targetBranchRef).isNotNull(); + assertThat(targetBranchRef.getObjectId().getName()).isEqualTo(branchRevision); + } + } + + private Ref getRef(Repository repo, String branchName) throws IOException { + return repo.getRefDatabase().exactRef(branchName); + } + + private Ref checkedGetRef(Repository repo, String branchName) { + try { + return repo.getRefDatabase().exactRef(branchName); + } catch (Exception e) { + logger.atSevere().withCause(e).log("failed to get ref %s in repo %s", branchName, repo); + return null; + } + } + + private void setReplicationSource( + String remoteName, String replicaSuffix, Optional<String> project) + throws IOException, ConfigInvalidException { + setReplicationSource(remoteName, Arrays.asList(replicaSuffix), project); + } + + private void setReplicationSource( + String remoteName, List<String> replicaSuffixes, Optional<String> project) + throws IOException, ConfigInvalidException { + + List<String> replicaUrls = + replicaSuffixes.stream() + .map(suffix -> gitPath.resolve("${name}" + suffix + ".git").toString()) + .collect(toList()); + config.setString("replication", null, "instanceLabel", remoteName); + config.setStringList("remote", remoteName, "url", replicaUrls); + config.setString("remote", remoteName, "apiUrl", getListenUrl()); + config.setString("remote", remoteName, "fetch", "+refs/*:refs/*"); + config.setInt("remote", remoteName, "timeout", 600); + config.setInt("remote", remoteName, "replicationDelay", TEST_REPLICATION_DELAY); + project.ifPresent(prj -> config.setString("remote", remoteName, "projects", prj)); + config.setBoolean("gerrit", null, "autoReload", true); + config.save(); + } + + private void setReplicationCredentials(String remoteName, String username, String password) + throws IOException { + secureConfig.setString("remote", remoteName, "username", username); + secureConfig.setString("remote", remoteName, "password", password); + secureConfig.save(); + } + + private void waitUntil(Supplier<Boolean> waitCondition) throws InterruptedException { + WaitUtil.waitUntil(waitCondition, TEST_TIMEOUT); + } + + 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 String getListenUrl() throws IOException, ConfigInvalidException { + FileBasedConfig gerritConfig = + new FileBasedConfig(sitePaths.etc_dir.resolve("gerrit.config").toFile(), FS.DETECTED); + gerritConfig.load(); + String listenUrl = gerritConfig.getString("httpd", null, "listenUrl"); + return listenUrl.endsWith("/") ? listenUrl.substring(0, listenUrl.length() - 1) : listenUrl; + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/WaitUtil.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/WaitUtil.java new file mode 100644 index 0000000..a99ac18 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/WaitUtil.java
@@ -0,0 +1,34 @@ +// Copyright (C) 2020 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.replication.pull; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.google.common.base.Stopwatch; +import java.time.Duration; +import java.util.function.Supplier; + +public class WaitUtil { + public static void waitUntil(Supplier<Boolean> waitCondition, Duration timeout) + throws InterruptedException { + Stopwatch stopwatch = Stopwatch.createStarted(); + while (!waitCondition.get()) { + if (stopwatch.elapsed().compareTo(timeout) > 0) { + throw new InterruptedException(); + } + MILLISECONDS.sleep(50); + } + } +}