Add support for dynamic config suites
By annotating methods with @ConfigSuite.Config config suites with
different configurations can be defined. However the set of config
suites is static, since one method per config suite must be defined.
Add a new @ConfigSuite.Configs annotation on a method that returns
Map<String, Config> to support dynamic config suites. The map keys
define the test suite names, while the values define the
configurations for the test suites. E.g.:
@ConfigSuite.Configs
public static Mapi<String, Config> configs() {
Config cfgA = new Config();
cfgA.setString("gerrit", null, "testValue", "a");
Config cfgB = new Config();
cfgB.setString("gerrit", null, "testValue", "b");
return ImmutableMap.of("testWithValueA", cfgA, "testWithValueB", cfgB);
}
In the follow-up changes this new annotation will be used to execute
query tests against old index versions.
Change-Id: I47e182bac6485457467f1d6573aca54ebf0ea9de
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
@@ -18,8 +18,10 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
import static java.lang.annotation.ElementType.FIELD;
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
import static java.lang.annotation.ElementType.METHOD;
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
import static java.util.stream.Collectors.toSet;
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
@@ -29,7 +31,10 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.junit.runner.Runner;
|
import org.junit.runner.Runner;
|
||||||
import org.junit.runners.BlockJUnit4ClassRunner;
|
import org.junit.runners.BlockJUnit4ClassRunner;
|
||||||
import org.junit.runners.Suite;
|
import org.junit.runners.Suite;
|
||||||
@@ -80,6 +85,21 @@ import org.junit.runners.model.InitializationError;
|
|||||||
* Additionally, config values used by <strong>default</strong> can be set in a method annotated
|
* Additionally, config values used by <strong>default</strong> can be set in a method annotated
|
||||||
* with {@code @ConfigSuite.Default}.
|
* with {@code @ConfigSuite.Default}.
|
||||||
*
|
*
|
||||||
|
* <p>In addition groups of tests for different configurations can be defined by annotating a method
|
||||||
|
* that returns a Map<String, Config> with {@link Configs}. The map keys define the test suite
|
||||||
|
* names, while the values define the configurations for the test suites.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@literal @}ConfigSuite.Configs
|
||||||
|
* public static Map<String, Config> configs() {
|
||||||
|
* Config cfgA = new Config();
|
||||||
|
* cfgA.setString("gerrit", null, "testValue", "a");
|
||||||
|
* Config cfgB = new Config();
|
||||||
|
* cfgB.setString("gerrit", null, "testValue", "b");
|
||||||
|
* return ImmutableMap.of("testWithValueA", cfgA, "testWithValueB", cfgB);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
* <p>The name of the config method corresponding to the currently-running test can be stored in a
|
* <p>The name of the config method corresponding to the currently-running test can be stored in a
|
||||||
* field annotated with {@code @ConfigSuite.Name}.
|
* field annotated with {@code @ConfigSuite.Name}.
|
||||||
*/
|
*/
|
||||||
@@ -94,6 +114,10 @@ public class ConfigSuite extends Suite {
|
|||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public static @interface Config {}
|
public static @interface Config {}
|
||||||
|
|
||||||
|
@Target({METHOD})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public static @interface Configs {}
|
||||||
|
|
||||||
@Target({FIELD})
|
@Target({FIELD})
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public static @interface Parameter {}
|
public static @interface Parameter {}
|
||||||
@@ -103,25 +127,29 @@ public class ConfigSuite extends Suite {
|
|||||||
public static @interface Name {}
|
public static @interface Name {}
|
||||||
|
|
||||||
private static class ConfigRunner extends BlockJUnit4ClassRunner {
|
private static class ConfigRunner extends BlockJUnit4ClassRunner {
|
||||||
private final Method configMethod;
|
private final org.eclipse.jgit.lib.Config cfg;
|
||||||
private final Field parameterField;
|
private final Field parameterField;
|
||||||
private final Field nameField;
|
private final Field nameField;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private ConfigRunner(
|
private ConfigRunner(
|
||||||
Class<?> clazz, Field parameterField, Field nameField, String name, Method configMethod)
|
Class<?> clazz,
|
||||||
|
Field parameterField,
|
||||||
|
Field nameField,
|
||||||
|
String name,
|
||||||
|
org.eclipse.jgit.lib.Config cfg)
|
||||||
throws InitializationError {
|
throws InitializationError {
|
||||||
super(clazz);
|
super(clazz);
|
||||||
this.parameterField = parameterField;
|
this.parameterField = parameterField;
|
||||||
this.nameField = nameField;
|
this.nameField = nameField;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.configMethod = configMethod;
|
this.cfg = cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object createTest() throws Exception {
|
public Object createTest() throws Exception {
|
||||||
Object test = getTestClass().getJavaClass().newInstance();
|
Object test = getTestClass().getJavaClass().newInstance();
|
||||||
parameterField.set(test, callConfigMethod(configMethod));
|
parameterField.set(test, new org.eclipse.jgit.lib.Config(cfg));
|
||||||
if (nameField != null) {
|
if (nameField != null) {
|
||||||
nameField.set(test, name);
|
nameField.set(test, name);
|
||||||
}
|
}
|
||||||
@@ -143,14 +171,23 @@ public class ConfigSuite extends Suite {
|
|||||||
private static List<Runner> runnersFor(Class<?> clazz) {
|
private static List<Runner> runnersFor(Class<?> clazz) {
|
||||||
Method defaultConfig = getDefaultConfig(clazz);
|
Method defaultConfig = getDefaultConfig(clazz);
|
||||||
List<Method> configs = getConfigs(clazz);
|
List<Method> configs = getConfigs(clazz);
|
||||||
|
Map<String, org.eclipse.jgit.lib.Config> configMap =
|
||||||
|
callConfigMapMethod(getConfigMap(clazz), configs);
|
||||||
|
|
||||||
Field parameterField = getOnlyField(clazz, Parameter.class);
|
Field parameterField = getOnlyField(clazz, Parameter.class);
|
||||||
checkArgument(parameterField != null, "No @ConfigSuite.Field found");
|
checkArgument(parameterField != null, "No @ConfigSuite.Field found");
|
||||||
Field nameField = getOnlyField(clazz, Name.class);
|
Field nameField = getOnlyField(clazz, Name.class);
|
||||||
List<Runner> result = Lists.newArrayListWithCapacity(configs.size() + 1);
|
List<Runner> result = Lists.newArrayListWithCapacity(configs.size() + 1);
|
||||||
try {
|
try {
|
||||||
result.add(new ConfigRunner(clazz, parameterField, nameField, null, defaultConfig));
|
result.add(
|
||||||
|
new ConfigRunner(
|
||||||
|
clazz, parameterField, nameField, null, callConfigMethod(defaultConfig)));
|
||||||
for (Method m : configs) {
|
for (Method m : configs) {
|
||||||
result.add(new ConfigRunner(clazz, parameterField, nameField, m.getName(), m));
|
result.add(
|
||||||
|
new ConfigRunner(clazz, parameterField, nameField, m.getName(), callConfigMethod(m)));
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, org.eclipse.jgit.lib.Config> e : configMap.entrySet()) {
|
||||||
|
result.add(new ConfigRunner(clazz, parameterField, nameField, e.getKey(), e.getValue()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (InitializationError e) {
|
} catch (InitializationError e) {
|
||||||
@@ -163,15 +200,20 @@ public class ConfigSuite extends Suite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Method getDefaultConfig(Class<?> clazz) {
|
private static Method getDefaultConfig(Class<?> clazz) {
|
||||||
|
return getAnnotatedMethod(clazz, Default.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method getConfigMap(Class<?> clazz) {
|
||||||
|
return getAnnotatedMethod(clazz, Configs.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Annotation> Method getAnnotatedMethod(
|
||||||
|
Class<?> clazz, Class<T> annotationClass) {
|
||||||
Method result = null;
|
Method result = null;
|
||||||
for (Method m : clazz.getMethods()) {
|
for (Method m : clazz.getMethods()) {
|
||||||
Default ann = m.getAnnotation(Default.class);
|
T ann = m.getAnnotation(annotationClass);
|
||||||
if (ann != null) {
|
if (ann != null) {
|
||||||
checkArgument(
|
checkArgument(result == null, "Multiple methods annotated with %s: %s, %s", ann, result, m);
|
||||||
result == null,
|
|
||||||
"Multiple methods annotated with @ConfigSuite.Method: %s, %s",
|
|
||||||
result,
|
|
||||||
m);
|
|
||||||
result = m;
|
result = m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,8 +225,7 @@ public class ConfigSuite extends Suite {
|
|||||||
for (Method m : clazz.getMethods()) {
|
for (Method m : clazz.getMethods()) {
|
||||||
Config ann = m.getAnnotation(Config.class);
|
Config ann = m.getAnnotation(Config.class);
|
||||||
if (ann != null) {
|
if (ann != null) {
|
||||||
checkArgument(
|
checkArgument(!m.getName().equals(DEFAULT), "%s cannot be named %s", ann, DEFAULT);
|
||||||
!m.getName().equals(DEFAULT), "@ConfigSuite.Config cannot be named %s", DEFAULT);
|
|
||||||
result.add(m);
|
result.add(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,6 +249,45 @@ public class ConfigSuite extends Suite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, org.eclipse.jgit.lib.Config> callConfigMapMethod(
|
||||||
|
Method m, List<Method> configs) {
|
||||||
|
if (m == null) {
|
||||||
|
return ImmutableMap.of();
|
||||||
|
}
|
||||||
|
checkArgument(Map.class.isAssignableFrom(m.getReturnType()), "%s must return Map", m);
|
||||||
|
Type[] types = ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments();
|
||||||
|
checkArgument(
|
||||||
|
String.class.isAssignableFrom((Class<?>) types[0]),
|
||||||
|
"The map returned by %s must have String as key",
|
||||||
|
m);
|
||||||
|
checkArgument(
|
||||||
|
org.eclipse.jgit.lib.Config.class.isAssignableFrom((Class<?>) types[1]),
|
||||||
|
"The map returned by %s must have Config as value",
|
||||||
|
m);
|
||||||
|
checkArgument((m.getModifiers() & Modifier.STATIC) != 0, "%s must be static", m);
|
||||||
|
checkArgument(m.getParameterTypes().length == 0, "%s must take no parameters", m);
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, org.eclipse.jgit.lib.Config> configMap =
|
||||||
|
(Map<String, org.eclipse.jgit.lib.Config>) m.invoke(null);
|
||||||
|
checkArgument(
|
||||||
|
!configMap.containsKey(DEFAULT),
|
||||||
|
"The map returned by %s cannot contain key %s (duplicate test suite name)",
|
||||||
|
m,
|
||||||
|
DEFAULT);
|
||||||
|
for (String name : configs.stream().map(cm -> cm.getName()).collect(toSet())) {
|
||||||
|
checkArgument(
|
||||||
|
!configMap.containsKey(name),
|
||||||
|
"The map returned by %s cannot contain key %s (duplicate test suite name)",
|
||||||
|
m,
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
return configMap;
|
||||||
|
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Field getOnlyField(Class<?> clazz, Class<? extends Annotation> ann) {
|
private static Field getOnlyField(Class<?> clazz, Class<? extends Annotation> ann) {
|
||||||
List<Field> fields = Lists.newArrayListWithExpectedSize(1);
|
List<Field> fields = Lists.newArrayListWithExpectedSize(1);
|
||||||
for (Field f : clazz.getFields()) {
|
for (Field f : clazz.getFields()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user