| // 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.MoreObjects; |
| import com.google.common.collect.Iterables; |
| 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.Annotation; |
| 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> |
| * {@literal @}RunWith(ConfigSuite.class) |
| * public abstract class MyAbstractTest { |
| * {@literal @}ConfigSuite.Parameter |
| * protected Config cfg; |
| * |
| * {@literal @}ConfigSuite.Config |
| * public static Config firstConfig() { |
| * Config cfg = new Config(); |
| * cfg.setString("gerrit", null, "testValue", "a"); |
| * } |
| * } |
| * |
| * public class MyTest extends MyAbstractTest { |
| * {@literal @}ConfigSuite.Config |
| * public static Config secondConfig() { |
| * Config cfg = new Config(); |
| * cfg.setString("gerrit", null, "testValue", "b"); |
| * } |
| * |
| * {@literal @}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> |
| * |
| * Additionally, config values used by <strong>default</strong> can be set |
| * in a method annotated with {@code @ConfigSuite.Default}. |
| * <p> |
| * The name of the config method corresponding to the currently-running test can |
| * be stored in a field annotated with {@code @ConfigSuite.Name}. |
| */ |
| public class ConfigSuite extends Suite { |
| private static final String DEFAULT = "default"; |
| |
| @Target({METHOD}) |
| @Retention(RUNTIME) |
| public static @interface Default { |
| } |
| |
| @Target({METHOD}) |
| @Retention(RUNTIME) |
| public static @interface Config { |
| } |
| |
| @Target({FIELD}) |
| @Retention(RUNTIME) |
| public static @interface Parameter { |
| } |
| |
| @Target({FIELD}) |
| @Retention(RUNTIME) |
| public static @interface Name { |
| } |
| |
| private static class ConfigRunner extends BlockJUnit4ClassRunner { |
| private final Method configMethod; |
| private final Field parameterField; |
| private final Field nameField; |
| private final String name; |
| |
| private ConfigRunner(Class<?> clazz, Field parameterField, Field nameField, |
| String name, Method configMethod) throws InitializationError { |
| super(clazz); |
| this.parameterField = parameterField; |
| this.nameField = nameField; |
| this.name = name; |
| this.configMethod = configMethod; |
| } |
| |
| @Override |
| public Object createTest() throws Exception { |
| Object test = getTestClass().getJavaClass().newInstance(); |
| parameterField.set(test, callConfigMethod(configMethod)); |
| if (nameField != null) { |
| nameField.set(test, name); |
| } |
| return test; |
| } |
| |
| @Override |
| protected String getName() { |
| return MoreObjects.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) { |
| Method defaultConfig = getDefaultConfig(clazz); |
| List<Method> configs = getConfigs(clazz); |
| Field parameterField = getOnlyField(clazz, Parameter.class); |
| checkArgument(parameterField != null, "No @ConfigSuite.Field found"); |
| Field nameField = getOnlyField(clazz, Name.class); |
| List<Runner> result = Lists.newArrayListWithCapacity(configs.size() + 1); |
| try { |
| result.add(new ConfigRunner( |
| clazz, parameterField, nameField, null, defaultConfig)); |
| for (Method m : configs) { |
| result.add(new ConfigRunner( |
| clazz, parameterField, nameField, m.getName(), m)); |
| } |
| return result; |
| } catch (InitializationError e) { |
| System.err.println("Errors initializing runners:"); |
| for (Throwable t : e.getCauses()) { |
| t.printStackTrace(); |
| } |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static Method getDefaultConfig(Class<?> clazz) { |
| Method result = null; |
| for (Method m : clazz.getMethods()) { |
| Default ann = m.getAnnotation(Default.class); |
| if (ann != null) { |
| checkArgument(result == null, |
| "Multiple methods annotated with @ConfigSuite.Method: %s, %s", |
| result, m); |
| result = m; |
| } |
| } |
| return result; |
| } |
| |
| 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 getOnlyField(Class<?> clazz, |
| Class<? extends Annotation> ann) { |
| List<Field> fields = Lists.newArrayListWithExpectedSize(1); |
| for (Field f : clazz.getFields()) { |
| if (f.getAnnotation(ann) != null) { |
| fields.add(f); |
| } |
| } |
| checkArgument(fields.size() <= 1, |
| "expected 1 @ConfigSuite.%s field, found: %s", |
| ann.getSimpleName(), fields); |
| return Iterables.getFirst(fields, null); |
| } |
| |
| public ConfigSuite(Class<?> clazz) throws InitializationError { |
| super(clazz, runnersFor(clazz)); |
| } |
| } |