From c27747cb9107b2e81c50baccf20a96719f96baf1 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 26 May 2017 21:15:27 +0000 Subject: Fix JUnitParamsRunner so it works with CTS sharding am: b03560c325 am: 2f41cd20c0 am: f58521195e am: 55edd63fa3 Bug:38419944 Change-Id: Idb0cf19a4fb6d7e9b6161a708c8c582f4393fdc4 (cherry picked from commit bf41e5d5462598381b2fe3ce7e0d5303450a4ca6) --- Android.mk | 27 ++++ README.google | 1 + src/main/java/junitparams/JUnitParamsRunner.java | 127 ++++++++------- .../internal/DeferredErrorFrameworkMethod.java | 39 +++++ .../internal/DescribableFrameworkMethod.java | 17 ++ .../internal/InstanceFrameworkMethod.java | 57 +++++++ .../internal/InvokableFrameworkMethod.java | 59 +++++++ .../internal/InvokeNonParameterisedMethod.java | 24 +++ .../internal/InvokeParameterisedMethod.java | 9 +- .../junitparams/internal/MethodBlockSupplier.java | 32 ++++ .../internal/NonParameterisedFrameworkMethod.java | 45 ++++++ .../internal/ParameterisedFrameworkMethod.java | 85 ++++++++++ .../internal/ParameterisedTestClassRunner.java | 177 --------------------- .../internal/ParameterisedTestMethodRunner.java | 108 ------------- .../internal/ParametrizedTestMethodsFilter.java | 37 ----- src/main/java/junitparams/internal/TestMethod.java | 90 ++++++++--- src/test/java/junitparams/FilterableTest.java | 15 ++ .../junitparams/ParametersReaderProvidersTest.java | 5 +- .../java/junitparams/internal/TestMethodTest.java | 6 +- .../naming/NamingStrategyIsUsedByRunnerTest.java | 4 + .../usage/person_example/PersonTest.java | 3 + 21 files changed, 542 insertions(+), 425 deletions(-) create mode 100644 src/main/java/junitparams/internal/DeferredErrorFrameworkMethod.java create mode 100644 src/main/java/junitparams/internal/DescribableFrameworkMethod.java create mode 100644 src/main/java/junitparams/internal/InstanceFrameworkMethod.java create mode 100644 src/main/java/junitparams/internal/InvokableFrameworkMethod.java create mode 100644 src/main/java/junitparams/internal/InvokeNonParameterisedMethod.java create mode 100644 src/main/java/junitparams/internal/MethodBlockSupplier.java create mode 100644 src/main/java/junitparams/internal/NonParameterisedFrameworkMethod.java create mode 100644 src/main/java/junitparams/internal/ParameterisedFrameworkMethod.java delete mode 100644 src/main/java/junitparams/internal/ParameterisedTestClassRunner.java delete mode 100644 src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java delete mode 100644 src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java diff --git a/Android.mk b/Android.mk index 57b1ac7..99545dc 100644 --- a/Android.mk +++ b/Android.mk @@ -53,6 +53,22 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ junit include $(BUILD_STATIC_JAVA_LIBRARY) +#------------------------------- +# build a host test jar +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src/test/java) +LOCAL_JAVA_RESOURCE_DIRS := src/test/resources +LOCAL_MODULE := junit-params-test-host +LOCAL_NO_STANDARD_LIBRARIES := true +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := \ + junit-params-host \ + junit-params-assertj-core-host +include $(BUILD_HOST_JAVA_LIBRARY) + #------------------------------- # build a target test jar # @@ -83,3 +99,14 @@ LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \ junit-params-assertj-core:lib/assertj-core-1.7.1.jar include $(BUILD_MULTI_PREBUILT) + +#------------------------------- +# prebuilt dependencies + +include $(CLEAR_VARS) + +LOCAL_IS_HOST_MODULE := true +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \ + junit-params-assertj-core-host:lib/assertj-core-1.7.1.jar + +include $(BUILD_MULTI_PREBUILT) diff --git a/README.google b/README.google index 1b6a564..47541ba 100644 --- a/README.google +++ b/README.google @@ -17,3 +17,4 @@ Local Modifications: 36541809 - Hard code the description name to be compatible with CTS and prevent use of @TestCaseName. Ignore tests broken by the above change. + 38419944 - Fix sharding on CTS. diff --git a/src/main/java/junitparams/JUnitParamsRunner.java b/src/main/java/junitparams/JUnitParamsRunner.java index 970529e..edd0ad4 100644 --- a/src/main/java/junitparams/JUnitParamsRunner.java +++ b/src/main/java/junitparams/JUnitParamsRunner.java @@ -1,19 +1,20 @@ package junitparams; -import java.util.ArrayList; import java.util.List; +import junitparams.internal.MethodBlockSupplier; import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; -import junitparams.internal.ParameterisedTestClassRunner; -import junitparams.internal.ParametrizedTestMethodsFilter; +import junitparams.internal.DescribableFrameworkMethod; +import junitparams.internal.InstanceFrameworkMethod; +import junitparams.internal.InvokableFrameworkMethod; +import junitparams.internal.NonParameterisedFrameworkMethod; +import junitparams.internal.ParameterisedFrameworkMethod; import junitparams.internal.TestMethod; /** @@ -385,22 +386,16 @@ import junitparams.internal.TestMethod; */ public class JUnitParamsRunner extends BlockJUnit4ClassRunner { - private ParametrizedTestMethodsFilter parametrizedTestMethodsFilter = new ParametrizedTestMethodsFilter(this); - private ParameterisedTestClassRunner parameterisedRunner; - private Description description; + private final MethodBlockSupplier methodBlockSupplier; public JUnitParamsRunner(Class klass) throws InitializationError { super(klass); - parameterisedRunner = new ParameterisedTestClassRunner(getTestClass()); - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - super.filter(filter); - // Android-changed: Applying a filter could change the description so invalidate any cached - // description. See b/36074730 - description = null; - this.parametrizedTestMethodsFilter = new ParametrizedTestMethodsFilter(this,filter); + methodBlockSupplier = new MethodBlockSupplier() { + @Override + public Statement getMethodBlock(InvokableFrameworkMethod method) { + return methodBlock(method); + } + }; } @Override @@ -412,74 +407,76 @@ public class JUnitParamsRunner extends BlockJUnit4ClassRunner { @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { - if (handleIgnored(method, notifier)) + DescribableFrameworkMethod describableMethod = getDescribableMethod(method); + if (handleIgnored(describableMethod, notifier)) return; - TestMethod testMethod = parameterisedRunner.testMethodFor(method); - if (parameterisedRunner.shouldRun(testMethod)){ - parameterisedRunner.runParameterisedTest(testMethod, methodBlock(method), notifier); + if (method instanceof ParameterisedFrameworkMethod) { + ParameterisedFrameworkMethod parameterisedFrameworkMethod = + (ParameterisedFrameworkMethod) method; + + List methods = parameterisedFrameworkMethod.getMethods(); + for (InstanceFrameworkMethod frameworkMethod : methods) { + frameworkMethod.run(methodBlockSupplier, notifier); + } + } + else if (describableMethod instanceof InvokableFrameworkMethod) { + ((InvokableFrameworkMethod) describableMethod).run(methodBlockSupplier, notifier); } - else{ - verifyMethodCanBeRunByStandardRunner(testMethod); - super.runChild(method, notifier); + else { + throw new IllegalStateException( + "Unsupported FrameworkMethod class: " + method.getClass()); } } - private void verifyMethodCanBeRunByStandardRunner(TestMethod testMethod) { - List errors = new ArrayList(); - testMethod.frameworkMethod().validatePublicVoidNoArg(false, errors); - if (!errors.isEmpty()) { - throw new RuntimeException(errors.get(0)); + /** + * Check that the supplied method is one that was originally in the list returned by + * {@link #computeTestMethods()}. + * + * @param method the method, must be an instance of {@link DescribableFrameworkMethod} + * @return the supplied method cast to {@link DescribableFrameworkMethod} + * @throws IllegalArgumentException if the supplied method is not a + * {@link DescribableFrameworkMethod} + */ + private DescribableFrameworkMethod getDescribableMethod(FrameworkMethod method) { + if (!(method instanceof DescribableFrameworkMethod)) { + throw new IllegalArgumentException( + "Unsupported FrameworkMethod class: " + method.getClass() + + ", expected a DescribableFrameworkMethod subclass"); } + + return (DescribableFrameworkMethod) method; } - private boolean handleIgnored(FrameworkMethod method, RunNotifier notifier) { - TestMethod testMethod = parameterisedRunner.testMethodFor(method); - if (testMethod.isIgnored()) - notifier.fireTestIgnored(describeMethod(method)); + private boolean handleIgnored(DescribableFrameworkMethod method, RunNotifier notifier) { + // A parameterised method that is ignored (either due to @Ignore or due to empty parameters) + // is treated as if it was a non-parameterised method. + boolean ignored = (method instanceof NonParameterisedFrameworkMethod) + && ((NonParameterisedFrameworkMethod) method).isIgnored(); + if (ignored) + notifier.fireTestIgnored(method.getDescription()); - return testMethod.isIgnored(); + return ignored; } @Override protected List computeTestMethods() { - return parameterisedRunner.computeFrameworkMethods(); + return TestMethod.listFrom(getTestClass()); } @Override protected Statement methodInvoker(FrameworkMethod method, Object test) { - Statement methodInvoker = parameterisedRunner.parameterisedMethodInvoker(method, test); - if (methodInvoker == null) - methodInvoker = super.methodInvoker(method, test); - - return methodInvoker; - } - - @Override - public Description getDescription() { - if (description == null) { - description = Description.createSuiteDescription(getName(), getTestClass().getAnnotations()); - List resultMethods = getListOfMethods(); - - for (FrameworkMethod method : resultMethods) - description.addChild(describeMethod(method)); + if (method instanceof InvokableFrameworkMethod) { + return ((InvokableFrameworkMethod) method).getInvokeStatement(test); } - - return description; + throw new IllegalStateException( + "Unsupported FrameworkMethod class: " + method.getClass() + + ", expected an InvokableFrameworkMethod subclass"); } - private List getListOfMethods() { - List frameworkMethods = parameterisedRunner.returnListOfMethods(); - return parametrizedTestMethodsFilter.filteredMethods(frameworkMethods); - } - - public Description describeMethod(FrameworkMethod method) { - Description child = parameterisedRunner.describeParameterisedMethod(method); - - if (child == null) - child = describeChild(method); - - return child; + @Override + protected Description describeChild(FrameworkMethod method) { + return getDescribableMethod(method).getDescription(); } /** diff --git a/src/main/java/junitparams/internal/DeferredErrorFrameworkMethod.java b/src/main/java/junitparams/internal/DeferredErrorFrameworkMethod.java new file mode 100644 index 0000000..b6e843e --- /dev/null +++ b/src/main/java/junitparams/internal/DeferredErrorFrameworkMethod.java @@ -0,0 +1,39 @@ +package junitparams.internal; + +import java.lang.reflect.Method; + +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.Statement; + +/** + * Encapsulates a {@link Throwable} that was caught during initialization so that it can be + * thrown during execution in order to preserve previous behavior. + */ +public class DeferredErrorFrameworkMethod extends InvokableFrameworkMethod { + + private final Throwable throwable; + + DeferredErrorFrameworkMethod(Method method, Description description, + Throwable throwable) { + super(method, description); + this.throwable = throwable; + } + + @Override + public Statement getInvokeStatement(Object test) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + throw throwable; + } + }; + } + + @Override + public void run(MethodBlockSupplier supplier, RunNotifier notifier) { + // Do not call the MethodBlockSupplier as that could introduce additional errors, simply + // throw the encapsulated Throwable immediately. + runMethodInvoker(notifier, getInvokeStatement(notifier), getDescription()); + } +} diff --git a/src/main/java/junitparams/internal/DescribableFrameworkMethod.java b/src/main/java/junitparams/internal/DescribableFrameworkMethod.java new file mode 100644 index 0000000..c529292 --- /dev/null +++ b/src/main/java/junitparams/internal/DescribableFrameworkMethod.java @@ -0,0 +1,17 @@ +package junitparams.internal; + +import java.lang.reflect.Method; + +import org.junit.runner.Describable; +import org.junit.runner.Description; +import org.junit.runners.model.FrameworkMethod; + +/** + * A {@link FrameworkMethod} that also provides a {@link Description}. + */ +public abstract class DescribableFrameworkMethod extends FrameworkMethod implements Describable { + + DescribableFrameworkMethod(Method method) { + super(method); + } +} diff --git a/src/main/java/junitparams/internal/InstanceFrameworkMethod.java b/src/main/java/junitparams/internal/InstanceFrameworkMethod.java new file mode 100644 index 0000000..ad70454 --- /dev/null +++ b/src/main/java/junitparams/internal/InstanceFrameworkMethod.java @@ -0,0 +1,57 @@ +package junitparams.internal; + +import java.lang.reflect.Method; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * A {@link FrameworkMethod} that represents an instance of an + * {@link ParameterisedFrameworkMethod}, that is the combination of the test method with the + * parameter set that it will be passed. + */ +public class InstanceFrameworkMethod extends InvokableFrameworkMethod { + + private final Description instanceDescription; + + private final Object parametersSet; + + /** + * Create an {@link InstanceFrameworkMethod}. + * + *

It has two {@link Description} instances because it has to provide different + * {@link Description} to {@link TestRule} instances than other usages in order to maintain + * backwards compatibility. + * + * @param method the test method + * @param description the description that is supplied to {@link TestRule} instances. + * @param instanceDescription the description used for all other purposes, e.g. filtering, + * {@link Runner#getDescription()} and {@link RunListener}. + * @param parametersSet the set of parameters to pass to the method. + */ + InstanceFrameworkMethod(Method method, Description description, + Description instanceDescription, Object parametersSet) { + super(method, description); + this.instanceDescription = instanceDescription; + this.parametersSet = parametersSet; + } + + @Override + public Statement getInvokeStatement(Object test) { + return new InvokeParameterisedMethod(this, test, parametersSet); + } + + Description getInstanceDescription() { + return instanceDescription; + } + + @Override + public void run(MethodBlockSupplier supplier, RunNotifier notifier) { + runMethodInvoker(notifier, supplier.getMethodBlock(this), getInstanceDescription()); + } +} diff --git a/src/main/java/junitparams/internal/InvokableFrameworkMethod.java b/src/main/java/junitparams/internal/InvokableFrameworkMethod.java new file mode 100644 index 0000000..0ed0bdb --- /dev/null +++ b/src/main/java/junitparams/internal/InvokableFrameworkMethod.java @@ -0,0 +1,59 @@ +package junitparams.internal; + +import java.lang.reflect.Method; +import junitparams.JUnitParamsRunner; +import org.junit.internal.AssumptionViolatedException; +import org.junit.internal.runners.model.EachTestNotifier; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * Base for {@link FrameworkMethod} classes that provide a {@link Statement} for invoking. + */ +public abstract class InvokableFrameworkMethod extends DescribableFrameworkMethod { + + private final Description description; + + InvokableFrameworkMethod(Method method, Description description) { + super(method); + this.description = description; + } + + @Override + public Description getDescription() { + return description; + } + + /** + * Create a {@link Statement} that when called will invoke the method. + * + *

This is usually called from the + * {@link JUnitParamsRunner#methodInvoker(FrameworkMethod, Object)} method via the + * {@link MethodBlockSupplier} which is usually called from within the + * {@link #run(MethodBlockSupplier, RunNotifier)} method. + * + * @param test + * the object on which the method will be invoked. + * @return the {@link Statement}. + */ + public abstract Statement getInvokeStatement(Object test); + + void runMethodInvoker(RunNotifier notifier, Statement methodInvoker, + Description methodWithParams) { + EachTestNotifier eachNotifier = new EachTestNotifier(notifier, methodWithParams); + eachNotifier.fireTestStarted(); + try { + methodInvoker.evaluate(); + } catch (AssumptionViolatedException e) { + eachNotifier.addFailedAssumption(e); + } catch (Throwable e) { + eachNotifier.addFailure(e); + } finally { + eachNotifier.fireTestFinished(); + } + } + + public abstract void run(MethodBlockSupplier supplier, RunNotifier notifier); +} diff --git a/src/main/java/junitparams/internal/InvokeNonParameterisedMethod.java b/src/main/java/junitparams/internal/InvokeNonParameterisedMethod.java new file mode 100644 index 0000000..a4256a2 --- /dev/null +++ b/src/main/java/junitparams/internal/InvokeNonParameterisedMethod.java @@ -0,0 +1,24 @@ +package junitparams.internal; + +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * JUnit invoker for non-parameterised test methods + */ +public class InvokeNonParameterisedMethod extends Statement { + + private final FrameworkMethod testMethod; + private final Object testClass; + + InvokeNonParameterisedMethod(FrameworkMethod testMethod, Object testClass) { + this.testMethod = testMethod; + this.testClass = testClass; + } + + @Override + public void evaluate() throws Throwable { + testMethod.invokeExplosively(testClass); + } + +} diff --git a/src/main/java/junitparams/internal/InvokeParameterisedMethod.java b/src/main/java/junitparams/internal/InvokeParameterisedMethod.java index 7d313c2..089bbe1 100644 --- a/src/main/java/junitparams/internal/InvokeParameterisedMethod.java +++ b/src/main/java/junitparams/internal/InvokeParameterisedMethod.java @@ -4,7 +4,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.math.BigDecimal; -import org.junit.runner.Description; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; @@ -23,12 +22,10 @@ public class InvokeParameterisedMethod extends Statement { private final Object[] params; private final FrameworkMethod testMethod; private final Object testClass; - private final String uniqueMethodId; - public InvokeParameterisedMethod(FrameworkMethod testMethod, Object testClass, Object params, int paramSetIdx) { + public InvokeParameterisedMethod(FrameworkMethod testMethod, Object testClass, Object params) { this.testMethod = testMethod; this.testClass = testClass; - this.uniqueMethodId = Utils.uniqueMethodId(paramSetIdx - 1, params, testMethod.getName()); try { if (params instanceof String) this.params = castParamsFromString((String) params); @@ -218,10 +215,6 @@ public class InvokeParameterisedMethod extends Statement { + testMethod.getName() + " method."); } - boolean matchesDescription(Description description) { - return description.hashCode() == uniqueMethodId.hashCode(); - } - @Override public void evaluate() throws Throwable { testMethod.invokeExplosively(testClass, params == null ? new Object[]{params} : params); diff --git a/src/main/java/junitparams/internal/MethodBlockSupplier.java b/src/main/java/junitparams/internal/MethodBlockSupplier.java new file mode 100644 index 0000000..ce14955 --- /dev/null +++ b/src/main/java/junitparams/internal/MethodBlockSupplier.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 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 junitparams.internal; + +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * Wraps the {@link Statement} provided by + * {@link InvokableFrameworkMethod#getInvokeStatement(Object)} with additional {@link Statement}. + * + *

This is essentially a method reference to + * {@link BlockJUnit4ClassRunner#methodBlock(FrameworkMethod)} + */ +public interface MethodBlockSupplier { + + Statement getMethodBlock(InvokableFrameworkMethod method); +} diff --git a/src/main/java/junitparams/internal/NonParameterisedFrameworkMethod.java b/src/main/java/junitparams/internal/NonParameterisedFrameworkMethod.java new file mode 100644 index 0000000..0e2d297 --- /dev/null +++ b/src/main/java/junitparams/internal/NonParameterisedFrameworkMethod.java @@ -0,0 +1,45 @@ +package junitparams.internal; + +import java.lang.reflect.Method; + +import org.junit.Ignore; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * A {@link FrameworkMethod} that represents an unparameterized method. + */ +public class NonParameterisedFrameworkMethod + extends InvokableFrameworkMethod { + + private final boolean ignored; + + /** + * Create a non-parameterised method. + * + * @param method the test method + * @param description the description of the method + * @param ignored true if the method should be ignore, either because the method has the + * {@link Ignore} annotation, or because it is parameterised but is given no parameters. + */ + NonParameterisedFrameworkMethod(Method method, Description description, boolean ignored) { + super(method, description); + this.ignored = ignored; + } + + @Override + public Statement getInvokeStatement(Object test) { + return new InvokeNonParameterisedMethod(this, test); + } + + @Override + public void run(MethodBlockSupplier supplier, RunNotifier notifier) { + runMethodInvoker(notifier, supplier.getMethodBlock(this), getDescription()); + } + + public boolean isIgnored() { + return ignored; + } +} diff --git a/src/main/java/junitparams/internal/ParameterisedFrameworkMethod.java b/src/main/java/junitparams/internal/ParameterisedFrameworkMethod.java new file mode 100644 index 0000000..f593f4f --- /dev/null +++ b/src/main/java/junitparams/internal/ParameterisedFrameworkMethod.java @@ -0,0 +1,85 @@ +package junitparams.internal; + +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runners.model.FrameworkMethod; + +/** + * A {@link FrameworkMethod} that represents a parameterized method. + * + *

This contains a list of {@link InstanceFrameworkMethod} that represent the individual + * instances of this method, one per parameter set. + */ +public class ParameterisedFrameworkMethod extends DescribableFrameworkMethod implements Filterable { + + /** + * The base description, used as a template when creating {@link Description}. + */ + private final Description baseDescription; + + /** + * The list of {@link InstanceFrameworkMethod} that represent individual instances of this + * method. + */ + private List instanceMethods; + + /** + * The {@link Description}, created lazily and updated after filtering. + */ + private Description description; + + public ParameterisedFrameworkMethod(Method method, Description baseDescription, + List instanceMethods) { + super(method); + this.baseDescription = baseDescription; + this.instanceMethods = instanceMethods; + } + + @Override + public Description getDescription() { + if (description == null) { + description = baseDescription.childlessCopy(); + for (InstanceFrameworkMethod instanceMethod : instanceMethods) { + description.addChild(instanceMethod.getInstanceDescription()); + } + } + + return description; + } + + public List getMethods() { + return instanceMethods; + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + int count = instanceMethods.size(); + for (Iterator i = instanceMethods.iterator(); i.hasNext(); ) { + InstanceFrameworkMethod instanceMethod = i.next(); + if (filter.shouldRun(instanceMethod.getInstanceDescription())) { + try { + filter.apply(instanceMethod); + } catch (NoTestsRemainException e) { + i.remove(); + } + } else { + i.remove(); + } + } + + if (instanceMethods.size() != count) { + // Some instance methods have been filtered out, so invalidate the description. + description = null; + } + + if (instanceMethods.isEmpty()) { + throw new NoTestsRemainException(); + } + } +} diff --git a/src/main/java/junitparams/internal/ParameterisedTestClassRunner.java b/src/main/java/junitparams/internal/ParameterisedTestClassRunner.java deleted file mode 100644 index 23daf88..0000000 --- a/src/main/java/junitparams/internal/ParameterisedTestClassRunner.java +++ /dev/null @@ -1,177 +0,0 @@ -package junitparams.internal; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Test; -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; - -/** - * Testclass-level functionalities to handle parameters from a JUnit runner - * class. - * - * @author Pawel Lipinski - */ -public class ParameterisedTestClassRunner { - - protected Map parameterisedMethods = new HashMap(); - protected Map testMethods = new HashMap(); - protected List testMethodsList; - - /** - * Creates a runner for a given test class. Computes all the test methods - * that are annotated as tests. Retrieves and caches all parameter values. - * - * @param testClass - */ - public ParameterisedTestClassRunner(TestClass testClass) { - computeTestMethods(testClass); - fillTestMethodsMap(); - computeFrameworkMethods(); - } - - protected void computeTestMethods(TestClass testClass) { - testMethodsList = TestMethod.listFrom(testClass.getAnnotatedMethods(Test.class), testClass); - } - - private void fillTestMethodsMap() { - for (TestMethod testMethod : testMethodsList) - testMethods.put(testMethod.frameworkMethod(), testMethod); - } - - /** - * Returns a list of FrameworkMethods. Handles both - * parameterised methods (counts them as many times as many paramsets they - * have) and nonparameterised methods (just counts them once). - * - * @return a list of FrameworkMethod objects - */ - public List computeFrameworkMethods() { - List resultMethods = new ArrayList(); - - for (TestMethod testMethod : testMethodsList) { - if (testMethod.isParameterised()) - addTestMethodForEachParamSet(resultMethods, testMethod); - else - addTestMethodOnce(resultMethods, testMethod); - } - - return resultMethods; - } - - /** - * Returns a list of FrameworkMethods - once per method, like - * there were no parameters. - * For JUnit to build names for IDE. - */ - public List returnListOfMethods() { - List resultMethods = new ArrayList(); - - for (TestMethod testMethod : testMethodsList) { - addTestMethodOnce(resultMethods, testMethod); - cacheMethodRunner(testMethod); - testMethod.warnIfNoParamsGiven(); - } - - return resultMethods; - } - - private void addTestMethodForEachParamSet(List resultMethods, TestMethod testMethod) { - if (testMethod.isNotIgnored()) { - int paramSetSize = testMethod.parametersSets().length; - for (int i = 0; i < paramSetSize; i++) - addTestMethodOnce(resultMethods, testMethod); - } else { - addTestMethodOnce(resultMethods, testMethod); - } - } - - private void addTestMethodOnce(List resultMethods, TestMethod testMethod) { - resultMethods.add(testMethod.frameworkMethod()); - } - - private void cacheMethodRunner(TestMethod testMethod) { - if (!parameterisedMethods.containsKey(testMethod)) - parameterisedMethods.put(testMethod, new ParameterisedTestMethodRunner(testMethod)); - } - - /** - * Returns a InvokeParameterisedMethod for parameterised methods and null - * for nonparameterised - * - * @param method Test method - * @param testClass - * @return a Statement with the invoker for the parameterised method - */ - public Statement parameterisedMethodInvoker(FrameworkMethod method, Object testClass) { - TestMethod testMethod = testMethods.get(method); - - if (!testMethod.isParameterised()) - return null; - - return buildMethodInvoker(method, testClass, testMethod); - } - - private Statement buildMethodInvoker(FrameworkMethod method, Object testClass, TestMethod testMethod) { - ParameterisedTestMethodRunner parameterisedMethod = parameterisedMethods.get(testMethod); - - return new InvokeParameterisedMethod( - method, testClass, parameterisedMethod.currentParamsFromAnnotation(), parameterisedMethod.count()); - } - - /** - * Tells if method should be run by this runner. - * - * @param testMethod - * @return true, iff testMethod should be run by this runner. - */ - public boolean shouldRun(TestMethod testMethod) { - return testMethod.isParameterised(); - } - - /** - * Executes parameterised method. - * - * @param method - * @param methodInvoker - * @param notifier - */ - public void runParameterisedTest(TestMethod method, Statement methodInvoker, RunNotifier notifier) { - parameterisedMethods.get(method).runTestMethod(methodInvoker, notifier); - } - - /** - * Returns description of a parameterised method. - * - * @param method TODO - * @return Description of a method or null if it's not parameterised. - */ - public Description describeParameterisedMethod(FrameworkMethod method) { - TestMethod testMethod = testMethods.get(method); - - if (!testMethod.isParameterised()) - return null; - - return testMethod.describe(); - } - - /** - * Returns a cached TestMethod object related to the given FrameworkMethod. - * This object has all the params already retrieved, so use this one and not - * TestMethod's constructor if you want to have everything retrieved once - * and cached. - * - * @param method - * @return a cached TestMethod instance - */ - public TestMethod testMethodFor(FrameworkMethod method) { - return testMethods.get(method); - } - -} diff --git a/src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java b/src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java deleted file mode 100644 index 9573048..0000000 --- a/src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java +++ /dev/null @@ -1,108 +0,0 @@ -package junitparams.internal; - -import java.lang.reflect.Field; - -import org.junit.internal.AssumptionViolatedException; -import org.junit.internal.runners.model.EachTestNotifier; -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.model.Statement; - -/** - * Testmethod-level functionalities for parameterised tests - * - * @author Pawel Lipinski - */ -public class ParameterisedTestMethodRunner { - - public final TestMethod method; - private int count; - - public ParameterisedTestMethodRunner(TestMethod testMethod) { - this.method = testMethod; - } - - public int nextCount() { - return count++; - } - - public int count() { - return count; - } - - Object currentParamsFromAnnotation() { - return method.parametersSets()[nextCount()]; - } - - void runTestMethod(Statement methodInvoker, RunNotifier notifier) { - Description methodWithParams = findChildForParams(methodInvoker, method.describe()); - - runMethodInvoker(notifier, methodInvoker, methodWithParams); - } - - private void runMethodInvoker(RunNotifier notifier, Statement methodInvoker, Description methodWithParams) { - EachTestNotifier eachNotifier = new EachTestNotifier(notifier, methodWithParams); - eachNotifier.fireTestStarted(); - try { - methodInvoker.evaluate(); - } catch (AssumptionViolatedException e) { - eachNotifier.addFailedAssumption(e); - } catch (Throwable e) { - eachNotifier.addFailure(e); - } finally { - eachNotifier.fireTestFinished(); - } - } - - private Description findChildForParams(Statement methodInvoker, Description methodDescription) { - if (System.getProperty("JUnitParams.flat") != null) - return methodDescription; - - InvokeParameterisedMethod parameterisedInvoker = findParameterisedMethodInvokerInChain(methodInvoker); - - for (Description child : methodDescription.getChildren()) { - if (parameterisedInvoker.matchesDescription(child)) - return child; - } - return null; - } - - private InvokeParameterisedMethod findParameterisedMethodInvokerInChain(Statement methodInvoker) { - while (methodInvoker != null && !(methodInvoker instanceof InvokeParameterisedMethod)) - methodInvoker = nextChainedInvoker(methodInvoker); - - if (methodInvoker == null) - throw new RuntimeException("Cannot find invoker for the parameterised method. Using wrong JUnit version?"); - - return (InvokeParameterisedMethod) methodInvoker; - } - - private Statement nextChainedInvoker(Statement methodInvoker) { - Field[] declaredFields = methodInvoker.getClass().getDeclaredFields(); - - for (Field field : declaredFields) { - Statement statement = statementOrNull(methodInvoker, field); - if (statement != null) - return statement; - } - - return null; - } - - private Statement statementOrNull(Statement methodInvoker, Field field) { - if (Statement.class.isAssignableFrom(field.getType())) - return getOriginalStatement(methodInvoker, field); - - return null; - } - - private Statement getOriginalStatement(Statement methodInvoker, Field field) { - field.setAccessible(true); - try { - return (Statement) field.get(methodInvoker); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } -} diff --git a/src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java b/src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java deleted file mode 100644 index 905934c..0000000 --- a/src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -package junitparams.internal; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.runner.manipulation.Filter; -import org.junit.runners.model.FrameworkMethod; - -import junitparams.JUnitParamsRunner; - -public class ParametrizedTestMethodsFilter { - private final junitparams.JUnitParamsRunner jUnitParamsRunner; - - private final Filter filter; - - public ParametrizedTestMethodsFilter(junitparams.JUnitParamsRunner jUnitParamsRunner, Filter filter) { - this.jUnitParamsRunner = jUnitParamsRunner; - this.filter = filter; - } - - public ParametrizedTestMethodsFilter(JUnitParamsRunner jUnitParamsRunner) { - this.jUnitParamsRunner = jUnitParamsRunner; - this.filter = Filter.ALL; - } - - public List filteredMethods(List frameworkMethods) { - List filteredMethods = new ArrayList(); - - for (FrameworkMethod frameworkMethod : frameworkMethods) { - if (filter.shouldRun(jUnitParamsRunner.describeMethod(frameworkMethod))) { - filteredMethods.add(frameworkMethod); - } - } - - return filteredMethods; - } -} \ No newline at end of file diff --git a/src/main/java/junitparams/internal/TestMethod.java b/src/main/java/junitparams/internal/TestMethod.java index 6125803..d0188ef 100644 --- a/src/main/java/junitparams/internal/TestMethod.java +++ b/src/main/java/junitparams/internal/TestMethod.java @@ -1,11 +1,13 @@ package junitparams.internal; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.Ignore; +import org.junit.Test; import org.junit.runner.Description; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.TestClass; @@ -22,11 +24,12 @@ import junitparams.naming.TestCaseNamingStrategy; */ public class TestMethod { private FrameworkMethod frameworkMethod; - FrameworkMethodAnnotations frameworkMethodAnnotations; + private FrameworkMethodAnnotations frameworkMethodAnnotations; private Class testClass; private ParametersReader parametersReader; private Object[] cachedParameters; private TestCaseNamingStrategy namingStrategy; + private DescribableFrameworkMethod describableFrameworkMethod; public TestMethod(FrameworkMethod method, TestClass testClass) { this.frameworkMethod = method; @@ -41,11 +44,13 @@ public class TestMethod { return frameworkMethod.getName(); } - public static List listFrom(List annotatedMethods, TestClass testClass) { - List methods = new ArrayList(); + public static List listFrom(TestClass testClass) { + List methods = new ArrayList(); - for (FrameworkMethod frameworkMethod : annotatedMethods) - methods.add(new TestMethod(frameworkMethod, testClass)); + for (FrameworkMethod frameworkMethod : testClass.getAnnotatedMethods(Test.class)) { + TestMethod testMethod = new TestMethod(frameworkMethod, testClass); + methods.add(testMethod.describableFrameworkMethod()); + } return methods; } @@ -72,11 +77,11 @@ public class TestMethod { return Arrays.equals(frameworkMethodParameterTypes, testMethodParameterTypes); } - Class testClass() { + private Class testClass() { return testClass; } - public boolean isIgnored() { + private boolean isIgnored() { return hasIgnoredAnnotation() || hasNoParameters(); } @@ -88,7 +93,7 @@ public class TestMethod { return isParameterised() && parametersSets().length == 0; } - public boolean isNotIgnored() { + private boolean isNotIgnored() { return !isIgnored(); } @@ -96,27 +101,64 @@ public class TestMethod { return frameworkMethodAnnotations.getAnnotation(annotationType); } - Description describe() { - if (isNotIgnored() && !describeFlat()) { - Description parametrised = Description.createSuiteDescription(name()); - Object[] params = parametersSets(); - for (int i = 0; i < params.length; i++) { - Object paramSet = params[i]; - String name = namingStrategy.getTestCaseName(i, paramSet); - String uniqueMethodId = Utils.uniqueMethodId(i, paramSet, name()); + private Description getDescription(Object[] params, int i) { + Object paramSet = params[i]; + String name = namingStrategy.getTestCaseName(i, paramSet); + String uniqueMethodId = Utils.uniqueMethodId(i, paramSet, name()); + + return Description.createTestDescription(testClass().getName(), name, uniqueMethodId); + } + + DescribableFrameworkMethod describableFrameworkMethod() { + if (describableFrameworkMethod == null) { + Description baseDescription = Description.createTestDescription( + testClass, name(), frameworkMethodAnnotations.allAnnotations()); + Method method = frameworkMethod.getMethod(); + try { + describableFrameworkMethod = + createDescribableFrameworkMethod(method, baseDescription); + } catch (IllegalStateException e) { + // Defer error until running. + describableFrameworkMethod = + new DeferredErrorFrameworkMethod(method, baseDescription, e); + } + } + + return describableFrameworkMethod; + } + + private DescribableFrameworkMethod createDescribableFrameworkMethod(Method method, Description baseDescription) { + if (isParameterised()) { + if (isNotIgnored()) { + Object[] parametersSets = parametersSets(); + List methods + = new ArrayList(); + for (int i = 0; i < parametersSets.length; i++) { + Object parametersSet = parametersSets[i]; + Description description = getDescription(parametersSets, i); + methods.add(new InstanceFrameworkMethod( + method, baseDescription.childlessCopy(), + description, parametersSet)); + } - parametrised.addChild( - Description.createTestDescription(testClass().getName(), name, uniqueMethodId) - ); + return new ParameterisedFrameworkMethod(method, baseDescription, methods); } - return parametrised; + + warnIfNoParamsGiven(); } else { - return Description.createTestDescription(testClass(), name(), frameworkMethodAnnotations.allAnnotations()); + verifyMethodCanBeRunByStandardRunner(frameworkMethod); } + + // The method to use if it was ignored or was parameterized but had no parameters. + return new NonParameterisedFrameworkMethod(method, baseDescription, isIgnored()); } - private boolean describeFlat() { - return System.getProperty("JUnitParams.flat") != null; + private void verifyMethodCanBeRunByStandardRunner(FrameworkMethod method) { + List errors = new ArrayList(); + method.validatePublicVoidNoArg(false, errors); + if (!errors.isEmpty()) { + throw new RuntimeException(errors.get(0)); + } } public Object[] parametersSets() { @@ -126,7 +168,7 @@ public class TestMethod { return cachedParameters; } - void warnIfNoParamsGiven() { + private void warnIfNoParamsGiven() { if (isNotIgnored() && isParameterised() && parametersSets().length == 0) System.err.println("Method " + name() + " gets empty list of parameters, so it's being ignored!"); } diff --git a/src/test/java/junitparams/FilterableTest.java b/src/test/java/junitparams/FilterableTest.java index 05b2b4c..97e1ea4 100644 --- a/src/test/java/junitparams/FilterableTest.java +++ b/src/test/java/junitparams/FilterableTest.java @@ -6,6 +6,7 @@ import org.junit.runner.JUnitCore; import org.junit.runner.Request; import org.junit.runner.Result; import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.NoTestsRemainException; import static org.assertj.core.api.Assertions.*; @@ -58,6 +59,20 @@ public class FilterableTest { assertThat(description.getChildren().get(0).getChildren()).hasSize(2); } + @Test + public void shouldApplyFiltersCumulatively() throws Exception { + JUnitParamsRunner runner = new JUnitParamsRunner(SampleTestCase.class); + // Remove the first method. + new SingleMethodFilter("firstTestMethod").apply(runner); + try { + // Now remove all instances of the second method. + new SingleMethodFilter("secondTestMethod").apply(runner); + fail("Filtering did not apply cumulatively"); + } catch (NoTestsRemainException expected) { + // expected + } + } + private Request requestSingleMethodRun(Class clazz, String methodName) { return Request.aClass(clazz).filterWith(new SingleMethodFilter(methodName)); } diff --git a/src/test/java/junitparams/ParametersReaderProvidersTest.java b/src/test/java/junitparams/ParametersReaderProvidersTest.java index c4a2bb8..8b4297f 100644 --- a/src/test/java/junitparams/ParametersReaderProvidersTest.java +++ b/src/test/java/junitparams/ParametersReaderProvidersTest.java @@ -1,6 +1,5 @@ package junitparams; -import junitparams.internal.ParameterisedTestMethodRunner; import junitparams.internal.TestMethod; import org.junit.Rule; import org.junit.Test; @@ -34,11 +33,11 @@ public class ParametersReaderProvidersTest { @Test public void shouldPutProviderClassNameInExceptionMessageForProviderWithNoValidMethods() { - ParameterisedTestMethodRunner runner = new ParameterisedTestMethodRunner(getTestMethodWithInvalidProvider()); + TestMethod testMethod = getTestMethodWithInvalidProvider(); exception.expect(RuntimeException.class); exception.expectMessage(ProviderClassWithNoValidMethods.class.getName()); - runner.method.parametersSets(); + testMethod.parametersSets(); } private TestMethod getTestMethodWithInvalidProvider() { diff --git a/src/test/java/junitparams/internal/TestMethodTest.java b/src/test/java/junitparams/internal/TestMethodTest.java index abfe5a3..62dc242 100644 --- a/src/test/java/junitparams/internal/TestMethodTest.java +++ b/src/test/java/junitparams/internal/TestMethodTest.java @@ -46,7 +46,7 @@ public class TestMethodTest { public void flatTestMethodStructure() throws Exception { System.setProperty("JUnitParams.flat", "true"); - Description description = plainTestMethod.describe(); + Description description = plainTestMethod.describableFrameworkMethod().getDescription(); assertEquals("for_others_to_work(junitparams.internal.TestMethodTest)", description.getDisplayName()); assertTrue(description.getChildren().isEmpty()); @@ -60,7 +60,7 @@ public class TestMethodTest { @Test public void hierarchicalTestMethodStructure() throws Exception { System.clearProperty("JUnitParams.flat"); - Description description = plainTestMethod.describe(); + Description description = plainTestMethod.describableFrameworkMethod().getDescription(); assertEquals("forOthersToWork", description.getDisplayName()); assertEquals("[0] a (forOthersToWork)(junitparams.internal.TestMethodTest)", description.getChildren().get(0).getDisplayName()); @@ -73,7 +73,7 @@ public class TestMethodTest { @Test public void hierarchicalArrayTestMethodStructure() throws Exception { System.clearProperty("JUnitParams.flat"); - Description description = arrayTestMethod.describe(); + Description description = arrayTestMethod.describableFrameworkMethod().getDescription(); assertEquals("forOthersToWorkWithArray", description.getDisplayName()); assertEquals("[0] a,b (forOthersToWorkWithArray)(junitparams.internal.TestMethodTest)", diff --git a/src/test/java/junitparams/naming/NamingStrategyIsUsedByRunnerTest.java b/src/test/java/junitparams/naming/NamingStrategyIsUsedByRunnerTest.java index 1e0ec24..effda7f 100644 --- a/src/test/java/junitparams/naming/NamingStrategyIsUsedByRunnerTest.java +++ b/src/test/java/junitparams/naming/NamingStrategyIsUsedByRunnerTest.java @@ -1,6 +1,7 @@ package junitparams.naming; import org.junit.AfterClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.Description; import org.junit.runner.Request; @@ -26,6 +27,9 @@ public class NamingStrategyIsUsedByRunnerTest { "[1] Well formed name of sampleMethod with param2" + className); } + // Android-changed: CTS and AndroidJUnitRunner rely on specific format to test names, changing + // them will prevent CTS and AndroidJUnitRunner from working properly; see b/36541809 + @Ignore @Test @Parameters({"param1", "param2"}) @TestCaseName("[{index}] Well formed name of {method} with {params}") diff --git a/src/test/java/junitparams/usage/person_example/PersonTest.java b/src/test/java/junitparams/usage/person_example/PersonTest.java index 927204e..b9f15c5 100644 --- a/src/test/java/junitparams/usage/person_example/PersonTest.java +++ b/src/test/java/junitparams/usage/person_example/PersonTest.java @@ -56,6 +56,9 @@ public class PersonTest { } } + // Android-changed: CTS and AndroidJUnitRunner rely on specific format to test names, changing + // them will prevent CTS and AndroidJUnitRunner from working properly; see b/36541809 + @Ignore @Test @Parameters(method = "adultValues") @TestCaseName("Is person with age {0} adult? It's {1} statement.") -- cgit v1.2.3