Test replication config and auto-reload
Unit-test the loading of the configuration through the
ReplicationFileBasedConfig and the auto-reload mechanism
provided by the AutoReloadConfigDecorator.
Change-Id: I9cf613c325d6adf5593a7ed7627b9f1774eb582d
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
new file mode 100644
index 0000000..da533c6
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
@@ -0,0 +1,115 @@
+// Copyright (C) 2019 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.getCurrentArguments;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.easymock.IAnswer;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.junit.Before;
+import org.junit.Ignore;
+
+@Ignore
+public abstract class AbstractConfigTest {
+ protected final Path sitePath;
+ protected final SitePaths sitePaths;
+ protected final Destination.Factory destinationFactoryMock;
+ protected final Path pluginDataPath;
+
+ static class FakeDestination extends Destination {
+ public final DestinationConfiguration config;
+
+ protected FakeDestination(DestinationConfiguration config) {
+ super(injectorMock(), null, null, null, null, null, null, null, null, null, config);
+ this.config = config;
+ }
+
+ private static Injector injectorMock() {
+ Injector injector = createNiceMock(Injector.class);
+ Injector childInjectorMock = createNiceMock(Injector.class);
+ expect(injector.createChildInjector((Module) anyObject())).andReturn(childInjectorMock);
+ replay(childInjectorMock);
+ replay(injector);
+ return injector;
+ }
+ }
+
+ AbstractConfigTest() throws IOException {
+ sitePath = createTempPath("site");
+ sitePaths = new SitePaths(sitePath);
+ pluginDataPath = createTempPath("data");
+ destinationFactoryMock = createMock(Destination.Factory.class);
+ }
+
+ @Before
+ public void setup() {
+ expect(destinationFactoryMock.create(isA(DestinationConfiguration.class)))
+ .andAnswer(
+ new IAnswer<Destination>() {
+ @Override
+ public Destination answer() throws Throwable {
+ return new FakeDestination((DestinationConfiguration) getCurrentArguments()[0]);
+ }
+ })
+ .anyTimes();
+ replay(destinationFactoryMock);
+ }
+
+ protected static Path createTempPath(String prefix) throws IOException {
+ return java.nio.file.Files.createTempDirectory(prefix);
+ }
+
+ protected FileBasedConfig newReplicationConfig() {
+ FileBasedConfig replicationConfig =
+ new FileBasedConfig(sitePaths.etc_dir.resolve("replication.config").toFile(), FS.DETECTED);
+ return replicationConfig;
+ }
+
+ protected void assertThatIsDestination(
+ Destination destination, String remoteName, String... remoteUrls) {
+ DestinationConfiguration destinationConfig = ((FakeDestination) destination).config;
+ assertThat(destinationConfig.getRemoteConfig().getName()).isEqualTo(remoteName);
+ assertThat(destinationConfig.getUrls()).containsExactlyElementsIn(remoteUrls);
+ }
+
+ protected void assertThatContainsDestination(
+ List<Destination> destinations, String remoteName, String... remoteUrls) {
+ List<Destination> matchingDestinations =
+ destinations.stream()
+ .filter(
+ (Destination dst) ->
+ ((FakeDestination) dst).config.getRemoteConfig().getName().equals(remoteName))
+ .collect(Collectors.toList());
+
+ assertThat(matchingDestinations).isNotEmpty();
+
+ assertThatIsDestination(matchingDestinations.get(0), remoteName, remoteUrls);
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
new file mode 100644
index 0000000..1b83625
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
@@ -0,0 +1,251 @@
+// Copyright (C) 2019 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.easymock.EasyMock.anyInt;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.util.Providers;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AutoReloadConfigDecoratorTest extends AbstractConfigTest {
+ private AutoReloadConfigDecorator autoReloadConfig;
+ private ReplicationQueue replicationQueueMock;
+ private WorkQueue workQueueMock;
+ private FakeExecutorService executorService = new FakeExecutorService();
+
+ public class FakeExecutorService implements ScheduledExecutorService {
+ public Runnable refreshCommand;
+
+ @Override
+ public void shutdown() {}
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return false;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {}
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(
+ Runnable command, long initialDelay, long period, TimeUnit unit) {
+ refreshCommand = command;
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(
+ Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return null;
+ }
+ }
+
+ public AutoReloadConfigDecoratorTest() throws IOException {
+ super();
+ }
+
+ @Override
+ @Before
+ public void setup() {
+ super.setup();
+
+ setupMocks();
+ }
+
+ private void setupMocks() {
+ replicationQueueMock = createNiceMock(ReplicationQueue.class);
+ replay(replicationQueueMock);
+
+ workQueueMock = createNiceMock(WorkQueue.class);
+ expect(workQueueMock.createQueue(anyInt(), anyObject(String.class))).andReturn(executorService);
+ replay(workQueueMock);
+ }
+
+ @Test
+ public void shouldLoadNotEmptyInitialReplicationConfig() throws Exception {
+ FileBasedConfig replicationConfig = newReplicationConfig();
+ String remoteName = "foo";
+ String remoteUrl = "ssh://git@git.somewhere.com/${name}";
+ replicationConfig.setString("remote", remoteName, "url", remoteUrl);
+ replicationConfig.save();
+
+ autoReloadConfig =
+ new AutoReloadConfigDecorator(
+ sitePaths,
+ destinationFactoryMock,
+ Providers.of(replicationQueueMock),
+ pluginDataPath,
+ "replication",
+ workQueueMock);
+
+ List<Destination> destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ assertThat(destinations).hasSize(1);
+ assertThatIsDestination(destinations.get(0), remoteName, remoteUrl);
+ }
+
+ @Test
+ public void shouldAutoReloadReplicationConfig() throws Exception {
+ FileBasedConfig replicationConfig = newReplicationConfig();
+ replicationConfig.setBoolean("gerrit", null, "autoReload", true);
+ String remoteName1 = "foo";
+ String remoteUrl1 = "ssh://git@git.foo.com/${name}";
+ replicationConfig.setString("remote", remoteName1, "url", remoteUrl1);
+ replicationConfig.save();
+
+ autoReloadConfig =
+ new AutoReloadConfigDecorator(
+ sitePaths,
+ destinationFactoryMock,
+ Providers.of(replicationQueueMock),
+ pluginDataPath,
+ "replication",
+ workQueueMock);
+ autoReloadConfig.startup(workQueueMock);
+
+ List<Destination> destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ assertThat(destinations).hasSize(1);
+ assertThatIsDestination(destinations.get(0), remoteName1, remoteUrl1);
+
+ TimeUnit.SECONDS.sleep(1); // Allow the filesystem to change the update TS
+
+ String remoteName2 = "bar";
+ String remoteUrl2 = "ssh://git@git.bar.com/${name}";
+ replicationConfig.setString("remote", remoteName2, "url", remoteUrl2);
+ replicationConfig.save();
+ executorService.refreshCommand.run();
+
+ destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ assertThat(destinations).hasSize(2);
+ assertThatContainsDestination(destinations, remoteName1, remoteUrl1);
+ assertThatContainsDestination(destinations, remoteName2, remoteUrl2);
+ }
+
+ @Test
+ public void shouldNotAutoReloadReplicationConfigIfDisabled() throws Exception {
+ String remoteName1 = "foo";
+ String remoteUrl1 = "ssh://git@git.foo.com/${name}";
+ FileBasedConfig replicationConfig = newReplicationConfig();
+ replicationConfig.setBoolean("gerrit", null, "autoReload", false);
+ replicationConfig.setString("remote", remoteName1, "url", remoteUrl1);
+ replicationConfig.save();
+
+ autoReloadConfig =
+ new AutoReloadConfigDecorator(
+ sitePaths,
+ destinationFactoryMock,
+ Providers.of(replicationQueueMock),
+ pluginDataPath,
+ "replication",
+ workQueueMock);
+ autoReloadConfig.startup(workQueueMock);
+
+ List<Destination> destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ assertThat(destinations).hasSize(1);
+ assertThatIsDestination(destinations.get(0), remoteName1, remoteUrl1);
+
+ TimeUnit.SECONDS.sleep(1); // Allow the filesystem to change the update TS
+
+ replicationConfig.setString("remote", "bar", "url", "ssh://git@git.bar.com/${name}");
+ replicationConfig.save();
+ executorService.refreshCommand.run();
+
+ assertThat(autoReloadConfig.getDestinations(FilterType.ALL)).isEqualTo(destinations);
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
new file mode 100644
index 0000000..36cc209
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2019 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.Test;
+
+public class ReplicationFileBasedConfigTest extends AbstractConfigTest {
+
+ public ReplicationFileBasedConfigTest() throws IOException {
+ super();
+ }
+
+ @Test
+ public void shouldLoadOneDestination() throws Exception {
+ String remoteName = "foo";
+ String remoteUrl = "ssh://git@git.somewhere.com/${name}";
+ FileBasedConfig config = newReplicationConfig();
+ config.setString("remote", remoteName, "url", remoteUrl);
+ config.save();
+
+ ReplicationFileBasedConfig replicationConfig = newReplicationFileBasedConfig();
+ List<Destination> destinations = replicationConfig.getDestinations(FilterType.ALL);
+ assertThat(destinations).hasSize(1);
+
+ assertThatIsDestination(destinations.get(0), remoteName, remoteUrl);
+ }
+
+ @Test
+ public void shouldLoadTwoDestinations() throws Exception {
+ String remoteName1 = "foo";
+ String remoteUrl1 = "ssh://git@git.somewhere.com/${name}";
+ String remoteName2 = "bar";
+ String remoteUrl2 = "ssh://git@git.elsewhere.com/${name}";
+ FileBasedConfig config = newReplicationConfig();
+ config.setString("remote", remoteName1, "url", remoteUrl1);
+ config.setString("remote", remoteName2, "url", remoteUrl2);
+ config.save();
+
+ ReplicationFileBasedConfig replicationConfig = newReplicationFileBasedConfig();
+ List<Destination> destinations = replicationConfig.getDestinations(FilterType.ALL);
+ assertThat(destinations).hasSize(2);
+
+ assertThatIsDestination(destinations.get(0), remoteName1, remoteUrl1);
+ assertThatIsDestination(destinations.get(1), remoteName2, remoteUrl2);
+ }
+
+ private ReplicationFileBasedConfig newReplicationFileBasedConfig()
+ throws ConfigInvalidException, IOException {
+ ReplicationFileBasedConfig replicationConfig =
+ new ReplicationFileBasedConfig(sitePaths, destinationFactoryMock, pluginDataPath);
+ return replicationConfig;
+ }
+}