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; + } +}