| Index: base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java
|
| diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a72c6962e7af4df78b89580ee522c10afac32c99
|
| --- /dev/null
|
| +++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java
|
| @@ -0,0 +1,239 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.base.test.params;
|
| +
|
| +import org.junit.Test;
|
| +import org.junit.runner.Runner;
|
| +import org.junit.runners.BlockJUnit4ClassRunner;
|
| +import org.junit.runners.Suite;
|
| +import org.junit.runners.model.FrameworkField;
|
| +import org.junit.runners.model.TestClass;
|
| +
|
| +import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
|
| +import org.chromium.base.test.params.ParameterAnnotations.MethodParameter;
|
| +import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
|
| +import org.chromium.base.test.params.ParameterizedRunnerDelegateFactory.ParameterizedRunnerDelegateInstantiationException;
|
| +
|
| +import java.lang.reflect.Modifier;
|
| +import java.util.ArrayList;
|
| +import java.util.Arrays;
|
| +import java.util.Collections;
|
| +import java.util.HashMap;
|
| +import java.util.List;
|
| +import java.util.Locale;
|
| +import java.util.Map;
|
| +
|
| +/**
|
| + * ParameterizedRunner generates a list of runners for each of class parameter set in a test class.
|
| + *
|
| + * ParameterizedRunner looks for {@code @ClassParameter} annotation in test class and
|
| + * generates a list of ParameterizedRunnerDelegate runners for each ParameterSet. The class
|
| + * runner also looks for {@code @MethodParameter} annotation, and creates a map that maps Strings
|
| + * value(tag) to ParameterSet List.
|
| + */
|
| +public final class ParameterizedRunner extends Suite {
|
| + private static final String TAG = "cr_ParameterizedRunner";
|
| + private final List<Runner> mRunners;
|
| +
|
| + /**
|
| + * Create a ParameterizedRunner to run test class
|
| + *
|
| + * @param klass the Class of the test class, test class should be atomic
|
| + * (extends only Object)
|
| + */
|
| + public ParameterizedRunner(Class<?> klass) throws Throwable {
|
| + super(klass, Collections.<Runner>emptyList()); // pass in empty list of runners
|
| + validate();
|
| + mRunners = createRunners(getTestClass());
|
| + }
|
| +
|
| + @Override
|
| + protected List<Runner> getChildren() {
|
| + return mRunners;
|
| + }
|
| +
|
| + /**
|
| + * ParentRunner calls collectInitializationErrors() to check for errors in Test class.
|
| + * Parameterized tests are written in unconventional ways, therefore, this method is
|
| + * overridden and validation is done seperately.
|
| + */
|
| + @Override
|
| + protected void collectInitializationErrors(List<Throwable> errors) {
|
| + // Do not call super collectInitializationErrors
|
| + }
|
| +
|
| + private void validate() throws Throwable {
|
| + validateNoNonStaticInnerClass();
|
| + validateOnlyOneConstructor();
|
| + validateInstanceMethods();
|
| + validateOnlyOneClassParameterField();
|
| + validateAtLeastOneParameterSetField();
|
| + }
|
| +
|
| + private void validateNoNonStaticInnerClass() throws Exception {
|
| + if (getTestClass().isANonStaticInnerClass()) {
|
| + throw new Exception("The inner class " + getTestClass().getName() + " is not static.");
|
| + }
|
| + }
|
| +
|
| + private void validateOnlyOneConstructor() throws Exception {
|
| + if (!hasOneConstructor()) {
|
| + throw new Exception("Test class should have exactly one public constructor");
|
| + }
|
| + }
|
| +
|
| + private boolean hasOneConstructor() {
|
| + return getTestClass().getJavaClass().getConstructors().length == 1;
|
| + }
|
| +
|
| + private void validateOnlyOneClassParameterField() {
|
| + if (getTestClass().getAnnotatedFields(ClassParameter.class).size() > 1) {
|
| + throw new IllegalParameterArgumentException(
|
| + "%s class has more than one @ClassParameter, only one is allowed");
|
| + }
|
| + }
|
| +
|
| + private void validateAtLeastOneParameterSetField() {
|
| + if (getTestClass().getAnnotatedFields(ClassParameter.class).isEmpty()
|
| + && getTestClass().getAnnotatedFields(MethodParameter.class).isEmpty()) {
|
| + throw new IllegalArgumentException(String.format(Locale.getDefault(),
|
| + "%s has no field annotated with @ClassParameter or @MethodParameter field, "
|
| + + "it should not use ParameterizedRunner",
|
| + getTestClass().getName()));
|
| + }
|
| + }
|
| +
|
| + private void validateInstanceMethods() throws Exception {
|
| + if (getTestClass().getAnnotatedMethods(Test.class).size() == 0) {
|
| + throw new Exception("No runnable methods");
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Return a list of runner delegates through ParameterizedRunnerDelegateFactory.
|
| + *
|
| + * For class parameter set: each class can only have one list of class parameter sets.
|
| + * Each parameter set will be used to create one runner.
|
| + *
|
| + * For method parameter set: a single list method parameter sets is associated with
|
| + * a string tag, an immutable map of string to parameter set list will be created and
|
| + * passed into factory for each runner delegate to create multiple tests. only one
|
| + * Runner will be created for a method that uses @MethodParameter, regardless of the
|
| + * number of ParameterSets in the associated list.
|
| + *
|
| + * @return a list of runners
|
| + * @throws ParameterizedTestInstantiationException if the test class is malformed.
|
| + * @throws ParameterizedRunnerDelegateInstantiationException if runner delegate can not
|
| + * be instantiated with constructor reflectively
|
| + * @throws IllegalAccessError if the field in tests are not accessible
|
| + */
|
| + static List<Runner> createRunners(TestClass testClass)
|
| + throws IllegalAccessException, ParameterizedTestInstantiationException,
|
| + ParameterizedRunnerDelegateInstantiationException {
|
| + List<ParameterSet> classParameterSetList;
|
| + if (testClass.getAnnotatedFields(ClassParameter.class).isEmpty()) {
|
| + classParameterSetList = new ArrayList<>();
|
| + classParameterSetList.add(null);
|
| + } else {
|
| + classParameterSetList = getParameterSetList(
|
| + testClass.getAnnotatedFields(ClassParameter.class).get(0), testClass);
|
| + validateWidth(classParameterSetList);
|
| + }
|
| +
|
| + Class<? extends ParameterizedRunnerDelegate> runnerDelegateClass =
|
| + getRunnerDelegateClass(testClass);
|
| + ParameterizedRunnerDelegateFactory factory = new ParameterizedRunnerDelegateFactory();
|
| + Map<String, List<ParameterSet>> tagToMethodParameterSetList =
|
| + Collections.unmodifiableMap(generateMethodParameterMap(testClass));
|
| + List<Runner> runnersForTestClass = new ArrayList<>();
|
| + for (ParameterSet classParameterSet : classParameterSetList) {
|
| + BlockJUnit4ClassRunner runner = (BlockJUnit4ClassRunner) factory.createRunner(
|
| + testClass, classParameterSet, tagToMethodParameterSetList, runnerDelegateClass);
|
| + runnersForTestClass.add(runner);
|
| + }
|
| + return runnersForTestClass;
|
| + }
|
| +
|
| + /**
|
| + * Returns a map between MethodParameter tags and corresponding ParameterSetLists.
|
| + */
|
| + static Map<String, List<ParameterSet>> generateMethodParameterMap(TestClass testClass)
|
| + throws IllegalAccessException {
|
| + Map<String, List<ParameterSet>> result = new HashMap<>();
|
| + for (FrameworkField field : testClass.getAnnotatedFields(MethodParameter.class)) {
|
| + List<ParameterSet> parameterSetList = getParameterSetList(field, testClass);
|
| + validateWidth(parameterSetList);
|
| + result.put(field.getAnnotation(MethodParameter.class).value(), parameterSetList);
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + /**
|
| + * Return an unmodifiable list of ParameterSet through a FrameworkField
|
| + */
|
| + static List<ParameterSet> getParameterSetList(FrameworkField field, TestClass testClass)
|
| + throws IllegalAccessException {
|
| + field.getField().setAccessible(true);
|
| + if (!Modifier.isStatic(field.getField().getModifiers())) {
|
| + throw new IllegalParameterArgumentException(String.format(Locale.getDefault(),
|
| + "ParameterSetList fields must be static, this field %s in %s is not",
|
| + field.getName(), testClass.getName()));
|
| + }
|
| + if (!(field.get(testClass.getJavaClass()) instanceof List)) {
|
| + throw new IllegalArgumentException(String.format(Locale.getDefault(),
|
| + "Fields with @ClassParameter or @MethodParameter annotations must be an"
|
| + + " instance of List, this field %s in %s is not list",
|
| + field.getName(), testClass.getName()));
|
| + }
|
| + @SuppressWarnings("unchecked") // checked above
|
| + List<ParameterSet> result = (List<ParameterSet>) field.get(testClass.getJavaClass());
|
| + return Collections.unmodifiableList(result);
|
| + }
|
| +
|
| + static void validateWidth(List<ParameterSet> parameterSetList) {
|
| + int lastSize = -1;
|
| + for (ParameterSet set : parameterSetList) {
|
| + if (lastSize == -1 || set.size() == lastSize) {
|
| + lastSize = set.size();
|
| + } else {
|
| + throw new IllegalParameterArgumentException(String.format(Locale.getDefault(),
|
| + "All ParameterSets in a list of ParameterSet must have equal"
|
| + + " length. The current ParameterSet (%s) contains %d parameters,"
|
| + + " while previous ParameterSet contains %d parameters",
|
| + Arrays.toString(set.getValues().toArray()), set.size(), lastSize));
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Get the runner delegate class for the test class if {@code @UseRunnerDelegate} is used.
|
| + * The default runner delegate is BaseJUnit4RunnerDelegate.class
|
| + */
|
| + private static Class<? extends ParameterizedRunnerDelegate> getRunnerDelegateClass(
|
| + TestClass testClass) {
|
| + if (testClass.getAnnotation(UseRunnerDelegate.class) != null) {
|
| + return testClass.getAnnotation(UseRunnerDelegate.class).value();
|
| + }
|
| + return BaseJUnit4RunnerDelegate.class;
|
| + }
|
| +
|
| + static class IllegalParameterArgumentException extends IllegalArgumentException {
|
| + public IllegalParameterArgumentException(String msg) {
|
| + super(msg);
|
| + }
|
| + }
|
| +
|
| + static class ParameterizedTestInstantiationException extends Exception {
|
| + ParameterizedTestInstantiationException(
|
| + TestClass testClass, String parameterSetString, Exception e) {
|
| + super(String.format(
|
| + "Test class %s can not be initiated, the provided parameters are %s,"
|
| + + " the required parameter types are %s",
|
| + testClass.getJavaClass().toString(), parameterSetString,
|
| + Arrays.toString(testClass.getOnlyConstructor().getParameterTypes())),
|
| + e);
|
| + }
|
| + }
|
| +}
|
|
|