Merge "Fix for: ConfigSuite calls @BeforeClass, @AfterClass, @ClassRule twice"
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 8064482..18a693f 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -202,7 +202,6 @@
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
@@ -365,7 +364,7 @@
}
}
- @AfterClass
+ @ConfigSuite.AfterConfig
public static void stopCommonServer() throws Exception {
if (commonServer != null) {
try {
diff --git a/java/com/google/gerrit/testing/ConfigSuite.java b/java/com/google/gerrit/testing/ConfigSuite.java
index d7ae397..3101c48 100644
--- a/java/com/google/gerrit/testing/ConfigSuite.java
+++ b/java/com/google/gerrit/testing/ConfigSuite.java
@@ -25,7 +25,9 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -35,11 +37,15 @@
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
+import org.junit.internal.runners.statements.RunAfters;
+import org.junit.internal.runners.statements.RunBefores;
+import org.junit.rules.TestRule;
import org.junit.runner.Runner;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.Suite;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
/**
* Suite to run tests with different {@code gerrit.config} values.
@@ -47,6 +53,9 @@
* <p>For each {@link Config} method in the class and base classes, a new group of tests is created
* with the {@link Parameter} field set to the config.
*
+ * <p>Additional actions can be executed before or after each group of tests using
+ * {@literal @}BeforeConfig, {@literal @}AfterConfig or {@literal @}ConfigRule annotations.
+ *
* <pre>
* {@literal @}RunWith(ConfigSuite.class)
* public abstract class MyAbstractTest {
@@ -128,6 +137,38 @@
@Retention(RUNTIME)
public static @interface Name {}
+ /**
+ * Annotation for methods which should be run after executing group of tests with a new
+ * configuration.
+ *
+ * <p>Works similar to {@link org.junit.AfterClass}, but a method can be executed multiple times
+ * if a test class provides multiple configs.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface AfterConfig {}
+
+ /**
+ * Annotation for methods which should be run before executing group of tests with a new
+ * configuration.
+ *
+ * <p>Works similar to {@link org.junit.BeforeClass}, but a method can be executed multiple times
+ * if a test class provides multiple configs.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface BeforeConfig {}
+
+ /**
+ * Annotation for fields or methods which wraps all tests with the same config
+ *
+ * <p>Works similar to {@link org.junit.ClassRule}, but Statement evaluates multiple time - ones
+ * for each config provided by a test class.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.FIELD, ElementType.METHOD})
+ public @interface ConfigRule {}
+
private static class ConfigRunner extends BlockJUnit4ClassRunner {
private final org.eclipse.jgit.lib.Config cfg;
private final Field parameterField;
@@ -168,6 +209,26 @@
String n = method.getName();
return name == null ? n : n + "[" + name + "]";
}
+
+ @Override
+ protected Statement withBeforeClasses(Statement statement) {
+ List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(BeforeConfig.class);
+ return befores.isEmpty() ? statement : new RunBefores(statement, befores, null);
+ }
+
+ @Override
+ protected Statement withAfterClasses(Statement statement) {
+ List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods(AfterConfig.class);
+ return afters.isEmpty() ? statement : new RunAfters(statement, afters, null);
+ }
+
+ @Override
+ protected List<TestRule> classRules() {
+ List<TestRule> result =
+ getTestClass().getAnnotatedMethodValues(null, ConfigRule.class, TestRule.class);
+ result.addAll(getTestClass().getAnnotatedFieldValues(null, ConfigRule.class, TestRule.class));
+ return result;
+ }
}
private static List<Runner> runnersFor(Class<?> clazz) {
diff --git a/java/com/google/gerrit/testing/SystemPropertiesTestRule.java b/java/com/google/gerrit/testing/SystemPropertiesTestRule.java
new file mode 100644
index 0000000..c7a3542
--- /dev/null
+++ b/java/com/google/gerrit/testing/SystemPropertiesTestRule.java
@@ -0,0 +1,67 @@
+// 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.google.gerrit.testing;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.common.Nullable;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.rules.ExternalResource;
+
+/** Setup system properties before tests and return previous value after tests are finished */
+public class SystemPropertiesTestRule extends ExternalResource {
+ ImmutableMap<String, Optional<String>> properties;
+ @Nullable ImmutableMap<String, Optional<String>> previousValues;
+
+ public SystemPropertiesTestRule(String key, String value) {
+ this(ImmutableMap.of(key, Optional.of(value)));
+ }
+
+ public SystemPropertiesTestRule(Map<String, Optional<String>> properties) {
+ this.properties = ImmutableMap.copyOf(properties);
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ super.before();
+ checkState(
+ previousValues == null,
+ "after() wasn't called after the previous call to the before() method");
+ ImmutableMap.Builder<String, Optional<String>> previousValuesBuilder = ImmutableMap.builder();
+ for (String key : properties.keySet()) {
+ previousValuesBuilder.put(key, Optional.ofNullable(System.getProperty(key)));
+ }
+ previousValues = previousValuesBuilder.build();
+ properties.entrySet().stream().forEach(this::setSystemProperty);
+ }
+
+ @Override
+ protected void after() {
+ checkState(previousValues != null, "before() wasn't called");
+ previousValues.entrySet().stream().forEach(this::setSystemProperty);
+ previousValues = null;
+ super.after();
+ }
+
+ private void setSystemProperty(Map.Entry<String, Optional<String>> keyValue) {
+ if (keyValue.getValue().isPresent()) {
+ System.setProperty(keyValue.getKey(), keyValue.getValue().get());
+ } else {
+ System.clearProperty(keyValue.getKey());
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
index f5e4e09..8e0d4bb 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
@@ -20,29 +20,19 @@
import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.testing.AbstractFakeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import com.google.gerrit.testing.SystemPropertiesTestRule;
import javax.inject.Inject;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Test;
/** Test to check that the expected index backend was bound depending on sys/env properties. */
public class DefaultIndexBindingIT extends AbstractDaemonTest {
+ @ClassRule
+ public static SystemPropertiesTestRule systemProperties =
+ new SystemPropertiesTestRule(IndexType.SYS_PROP, "");
@Inject private ChangeIndexCollection changeIndex;
- private static String propertyBeforeTest;
-
- @BeforeClass
- public static void setup() {
- propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
- System.setProperty(IndexType.SYS_PROP, "");
- }
-
- @AfterClass
- public static void teardown() {
- System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
- }
-
@Test
public void fakeIsBoundByDefault() throws Exception {
assertThat(System.getProperty(IndexType.SYS_PROP)).isEmpty();
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
index 4122426..acb2e5a 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
@@ -20,29 +20,19 @@
import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.testing.AbstractFakeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import com.google.gerrit.testing.SystemPropertiesTestRule;
import javax.inject.Inject;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Test;
/** Test to check that the expected index backend was bound depending on sys/env properties. */
public class FakeIndexBindingIT extends AbstractDaemonTest {
+ @ClassRule
+ public static SystemPropertiesTestRule systemProperties =
+ new SystemPropertiesTestRule(IndexType.SYS_PROP, "fake");
@Inject private ChangeIndexCollection changeIndex;
- private static String propertyBeforeTest;
-
- @BeforeClass
- public static void setup() {
- propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
- System.setProperty(IndexType.SYS_PROP, "fake");
- }
-
- @AfterClass
- public static void teardown() {
- System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
- }
-
@Test
public void fakeIsBoundWhenConfigured() throws Exception {
assertThat(System.getProperty(IndexType.SYS_PROP)).isEqualTo("fake");
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
index 31e31fd..5dd6f01 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
@@ -20,29 +20,19 @@
import com.google.gerrit.index.IndexType;
import com.google.gerrit.lucene.LuceneChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import com.google.gerrit.testing.SystemPropertiesTestRule;
import javax.inject.Inject;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Test;
/** Test to check that the expected index backend was bound depending on sys/env properties. */
public class LuceneIndexBindingIT extends AbstractDaemonTest {
+ @ClassRule
+ public static SystemPropertiesTestRule systemProperties =
+ new SystemPropertiesTestRule(IndexType.SYS_PROP, "lucene");
@Inject private ChangeIndexCollection changeIndex;
- private static String propertyBeforeTest;
-
- @BeforeClass
- public static void setup() {
- propertyBeforeTest = System.getProperty(IndexType.SYS_PROP);
- System.setProperty(IndexType.SYS_PROP, "lucene");
- }
-
- @AfterClass
- public static void teardown() {
- System.setProperty(IndexType.SYS_PROP, propertyBeforeTest);
- }
-
@Test
public void luceneIsBoundWhenConfigured() throws Exception {
assertThat(System.getProperty(IndexType.SYS_PROP)).isEqualTo("lucene");
diff --git a/javatests/com/google/gerrit/testing/BUILD b/javatests/com/google/gerrit/testing/BUILD
index 5774707..9443b0d 100644
--- a/javatests/com/google/gerrit/testing/BUILD
+++ b/javatests/com/google/gerrit/testing/BUILD
@@ -7,6 +7,7 @@
deps = [
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/testing/ConfigSuiteTest.java b/javatests/com/google/gerrit/testing/ConfigSuiteTest.java
new file mode 100644
index 0000000..1ec30da
--- /dev/null
+++ b/javatests/com/google/gerrit/testing/ConfigSuiteTest.java
@@ -0,0 +1,266 @@
+// 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.google.gerrit.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import com.google.gerrit.testing.ConfigSuite.AfterConfig;
+import com.google.gerrit.testing.ConfigSuite.BeforeConfig;
+import com.google.gerrit.testing.ConfigSuite.ConfigRule;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.Statement;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConfigSuiteTest {
+ @Mock private ConfigBasedTestListener configBasedTestListener;
+ private RunNotifier notifier;
+
+ @Before
+ public void setUp() {
+ notifier = new RunNotifier();
+ ConfigBasedTest.setConfigBasedTestListener(configBasedTestListener);
+ }
+
+ @After
+ public void tearDown() {
+ ConfigBasedTest.setConfigBasedTestListener(null);
+ }
+
+ @Test
+ public void methodsExecuteInCorrectOrder() throws Exception {
+ InOrder inOrder = Mockito.inOrder(configBasedTestListener);
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).classRuleExecuted();
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).beforeClassExecuted();
+ // default config
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).configRuleExectued();
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).beforeConfigExecuted();
+ inOrder.verify(configBasedTestListener, Mockito.times(2)).testExecuted(any(), any(), any());
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).afterConfigExecuted();
+ // first config
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).configRuleExectued();
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).beforeConfigExecuted();
+ inOrder.verify(configBasedTestListener, Mockito.times(2)).testExecuted(any(), any(), any());
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).afterConfigExecuted();
+ // second config
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).configRuleExectued();
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).beforeConfigExecuted();
+ inOrder.verify(configBasedTestListener, Mockito.times(2)).testExecuted(any(), any(), any());
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).afterConfigExecuted();
+
+ inOrder.verify(configBasedTestListener, Mockito.times(1)).afterClassExecuted();
+ }
+
+ @Test
+ public void beforeClassExecutedOnce() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ verify(configBasedTestListener, Mockito.times(1)).beforeClassExecuted();
+ }
+
+ @Test
+ public void afterClassExecutedOnce() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ verify(configBasedTestListener, Mockito.times(1)).afterClassExecuted();
+ }
+
+ @Test
+ public void classRuleExecutedOnlyOnce() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ verify(configBasedTestListener, Mockito.times(1)).classRuleExecuted();
+ }
+
+ @Test
+ public void beforeConfigExecutedForEachConfig() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ // default, firstConfig, secondConfig
+ verify(configBasedTestListener, Mockito.times(3)).beforeConfigExecuted();
+ }
+
+ @Test
+ public void afterConfigExecutedForEachConfig() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ // default, firstConfig, secondConfig
+ verify(configBasedTestListener, Mockito.times(3)).afterConfigExecuted();
+ }
+
+ @Test
+ public void configRuleExecutedForEachConfig() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ // default, firstConfig, secondConfig
+ verify(configBasedTestListener, Mockito.times(3)).afterConfigExecuted();
+ }
+
+ @Test
+ public void testsExecuteWithCorrectConfigAndName() throws Exception {
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ verify(configBasedTestListener, Mockito.times(6)).testExecuted(any(), any(), any());
+
+ verify(configBasedTestListener, Mockito.times(1)).testExecuted("test1", "default", null);
+ verify(configBasedTestListener, Mockito.times(1)).testExecuted("test2", "default", null);
+ verify(configBasedTestListener, Mockito.times(1))
+ .testExecuted("test1", "firstValue", "firstConfig");
+ verify(configBasedTestListener, Mockito.times(1))
+ .testExecuted("test2", "firstValue", "firstConfig");
+ verify(configBasedTestListener, Mockito.times(1))
+ .testExecuted("test1", "secondValue", "secondConfig");
+ verify(configBasedTestListener, Mockito.times(1))
+ .testExecuted("test2", "secondValue", "secondConfig");
+ }
+
+ @Test
+ public void testResultWasSuccessful() throws Exception {
+ Result result = new Result();
+ notifier.addListener(result.createListener());
+ new ConfigSuite(ConfigBasedTest.class).run(notifier);
+ assertThat(result.wasSuccessful()).isTrue();
+ }
+
+ @Test
+ public void failedTestResultNotMissed() throws Exception {
+ Result result = new Result();
+ notifier.addListener(result.createListener());
+ new ConfigSuite(FailedConfigBasedTest.class).run(notifier);
+ // 3 fails with 3 different configs
+ assertThat(result.wasSuccessful()).isFalse();
+ assertThat(result.getFailureCount()).isEqualTo(3);
+ }
+
+ interface ConfigBasedTestListener {
+ void beforeClassExecuted();
+
+ void afterClassExecuted();
+
+ void classRuleExecuted();
+
+ void configRuleExectued();
+
+ void testExecuted(String testName, String testValue, String configName);
+
+ void beforeConfigExecuted();
+
+ void afterConfigExecuted();
+ }
+
+ public static class ConfigBasedTest {
+ private static ConfigBasedTestListener listener;
+
+ public static void setConfigBasedTestListener(ConfigBasedTestListener listener) {
+ ConfigBasedTest.listener = listener;
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ listener.beforeClassExecuted();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ listener.afterClassExecuted();
+ }
+
+ @ClassRule
+ public static TestRule classRule =
+ new TestRule() {
+ @Override
+ public Statement apply(Statement statement, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ listener.classRuleExecuted();
+ statement.evaluate();
+ }
+ };
+ }
+ };
+
+ @ConfigRule
+ public static TestRule configRule =
+ new TestRule() {
+ @Override
+ public Statement apply(Statement statement, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ listener.configRuleExectued();
+ statement.evaluate();
+ }
+ };
+ }
+ };
+
+ @BeforeConfig
+ public static void beforeConfig() {
+ listener.beforeConfigExecuted();
+ }
+
+ @AfterConfig
+ public static void afterConfig() {
+ listener.afterConfigExecuted();
+ }
+
+ @ConfigSuite.Config
+ public static Config firstConfig() {
+ Config cfg = new Config();
+ cfg.setString("gerrit", null, "testValue", "firstValue");
+ return cfg;
+ }
+
+ @ConfigSuite.Config
+ public static Config secondConfig() {
+ Config cfg = new Config();
+ cfg.setString("gerrit", null, "testValue", "secondValue");
+ return cfg;
+ }
+
+ @ConfigSuite.Parameter public Config config;
+ @ConfigSuite.Name public String name;
+
+ @Test
+ public void test1() {
+ String testValue = config.getString("gerrit", null, "testValue");
+ listener.testExecuted("test1", testValue != null ? testValue : "default", name);
+ }
+
+ @Test
+ public void test2() {
+ String testValue = config.getString("gerrit", null, "testValue");
+ listener.testExecuted("test2", testValue != null ? testValue : "default", name);
+ }
+ }
+
+ public static class FailedConfigBasedTest extends ConfigBasedTest {
+ @Test
+ public void failedTest() {
+ assertThat(true).isFalse();
+ }
+ }
+}