Run acceptance tests with custom suite to set Configs

@Parameterized doesn't allow for running a single test method in
Eclipse, since Eclipse looks for a test name exactly matching the
method name, and @Parameterized always appends "[name]" to method
names.

Use a custom suite that avoids this problem by not appending anything
to the name for the default config. Running a single test method in
Eclipse now chooses the default (empty) config. This is arguably not
as nice as if it ran all permutations with that name, but it at least
chooses the least surprising option.

Since this suite only deals with setting a single field of a single
type on the test, it can be simpler than the more general
implementation of @Parameterized. It also has the advantage that
subclasses can provide their own @ConfigSuite.Config implementations
to add additional permutations at the per-acceptance-test level,
rather than globally.

Change-Id: I2a83f789f922d894c1bd111c1339f7ca9aacab00
This commit is contained in:
Dave Borowitz 2014-02-03 12:49:09 -08:00
parent afb276e22e
commit 9760cdc2b2
2 changed files with 189 additions and 29 deletions

View File

@ -14,7 +14,6 @@
package com.google.gerrit.acceptance; package com.google.gerrit.acceptance;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.acceptance.GitUtil.initSsh; import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -22,6 +21,7 @@ import com.google.common.base.Joiner;
import com.google.gerrit.extensions.common.ListChangesOption; import com.google.gerrit.extensions.common.ListChangesOption;
import com.google.gerrit.server.OutputFormat; import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.change.ChangeJson.ChangeInfo; import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -31,39 +31,13 @@ import org.junit.Rule;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.model.Statement; import org.junit.runners.model.Statement;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
@RunWith(Parameterized.class) @RunWith(ConfigSuite.class)
public abstract class AbstractDaemonTest { public abstract class AbstractDaemonTest {
private static class NamedConfig extends Config { @ConfigSuite.Parameter
private final String name;
private NamedConfig(String name) {
this.name = checkNotNull(name);
}
@Override
public String toString() {
return name;
}
}
@Parameters(name = "{0}")
public static Iterable<Object[]> configs() {
Config defaultConfig = new NamedConfig("default");
return Arrays.asList(new Object[][]{
{defaultConfig},
});
}
@Parameter
public Config baseConfig; public Config baseConfig;
@Inject @Inject

View File

@ -0,0 +1,186 @@
// Copyright (C) 2014 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.testutil;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
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 java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
/**
* Suite to run tests with different {@code gerrit.config} values.
* <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.
*
* <pre>
* @RunWith(ConfigSuite.class)
* public abstract class MyAbstractTest {
* @ConfigSuite.Parameter
* protected Config cfg;
*
* @ConfigSuite.Config
* public static Config firstConfig() {
* Config cfg = new Config();
* cfg.setString("gerrit", null, "testValue", "a");
* }
* }
*
* public class MyTest {
* @ConfigSuite.Config
* public static Config secondConfig() {
* Config cfg = new Config();
* cfg.setString("gerrit", null, "testValue", "b");
* }
*
* @Test
* public void myTest() {
* // Test using cfg.
* }
* }
* </pre>
*
* This creates a suite of tests with three groups:
* <ul>
* <li><strong>default</strong>: {@code MyTest.myTest}</li>
* <li><strong>firstConfig</strong>: {@code MyTest.myTest[firstConfig]}</li>
* <li><strong>secondConfig</strong>: {@code MyTest.myTest[secondConfig]}</li>
* </ul>
*/
public class ConfigSuite extends Suite {
private static final String DEFAULT = "default";
@Target({METHOD})
@Retention(RUNTIME)
public static @interface Config {
}
@Target({FIELD})
@Retention(RUNTIME)
public static @interface Parameter {
}
private static class ConfigRunner extends BlockJUnit4ClassRunner {
private final Method configMethod;
private final Field parameterField;
private final String name;
private ConfigRunner(Class<?> clazz, Field parameterField, String name,
Method configMethod) throws InitializationError {
super(clazz);
this.parameterField = parameterField;
this.name = name;
this.configMethod = configMethod;
}
@Override
public Object createTest() throws Exception {
Object test = getTestClass().getJavaClass().newInstance();
parameterField.set(test, callConfigMethod(configMethod));
return test;
}
@Override
protected String getName() {
return Objects.firstNonNull(name, DEFAULT);
}
@Override
protected String testName(FrameworkMethod method) {
String n = method.getName();
return name == null ? n : n + "[" + name + "]";
}
}
private static List<Runner> runnersFor(Class<?> clazz) {
List<Method> configs = getConfigs(clazz);
Field field = getParameterField(clazz);
List<Runner> result = Lists.newArrayListWithCapacity(configs.size() + 1);
try {
result.add(new ConfigRunner(clazz, field, null, null));
for (Method m : configs) {
result.add(new ConfigRunner(clazz, field, m.getName(), m));
}
return result;
} catch (InitializationError e) {
throw new RuntimeException(e);
}
}
private static List<Method> getConfigs(Class<?> clazz) {
List<Method> result = Lists.newArrayListWithExpectedSize(3);
for (Method m : clazz.getMethods()) {
Config ann = m.getAnnotation(Config.class);
if (ann != null) {
checkArgument(!m.getName().equals(DEFAULT),
"@ConfigSuite.Config cannot be named %s", DEFAULT);
result.add(m);
}
}
return result;
}
private static org.eclipse.jgit.lib.Config callConfigMethod(Method m) {
if (m == null) {
return new org.eclipse.jgit.lib.Config();
}
checkArgument(
org.eclipse.jgit.lib.Config.class.isAssignableFrom(m.getReturnType()),
"%s must return Config", m);
checkArgument((m.getModifiers() & Modifier.STATIC) != 0,
"%s must be static", m);
checkArgument(m.getParameterTypes().length == 0,
"%s must take no parameters", m);
try {
return (org.eclipse.jgit.lib.Config) m.invoke(null);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
}
private static Field getParameterField(Class<?> clazz) {
List<Field> fields = Lists.newArrayListWithExpectedSize(1);
for (Field f : clazz.getFields()) {
if (f.getAnnotation(Parameter.class) != null) {
fields.add(f);
}
}
checkArgument(fields.size() == 1,
"expected 1 @ConfigSuite.Parameter field, found: %s", fields);
return fields.get(0);
}
public ConfigSuite(Class<?> clazz) throws InitializationError {
super(clazz, runnersFor(clazz));
}
}