Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3908)

Unified Diff: base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java

Issue 2568633002: Create Next Gen Parameter Test Framework for JUnit4 (Closed)
Patch Set: rebase Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698