package thredds.junit4; import org.junit.Ignore; import org.junit.Test; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.internal.runners.model.ReflectiveCallable; import org.junit.internal.runners.statements.ExpectException; import org.junit.internal.runners.statements.Fail; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.Suite; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.test.annotation.ProfileValueUtils; import org.springframework.test.annotation.Repeat; import org.springframework.test.annotation.Timed; import org.springframework.test.context.TestContextManager; import org.springframework.test.context.junit4.statements.*; import org.springframework.util.ReflectionUtils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * <p> * The custom runner <code>Parameterized</code> implements parameterized tests. * When running a parameterized test class, instances are created for the * cross-product of the test methods and the test data elements. * </p> * * For example, to test a Fibonacci function, write: * * <pre> * @RunWith(Parameterized.class) * public class FibonacciTest { * @Parameters * public static List<Object[]> data() { * return Arrays.asList(new Object[][] { * Fibonacci, * { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, * { 6, 8 } } }); * } * * private int fInput; * * private int fExpected; * * public FibonacciTest(int input, int expected) { * fInput= input; * fExpected= expected; * } * * @Test * public void test() { * assertEquals(fExpected, Fibonacci.compute(fInput)); * } * } * </pre> * * <p> * Each instance of <code>FibonacciTest</code> will be constructed using the * two-argument constructor and the data values in the * <code>@Parameters</code> method. * </p> * * @see "https://jira.springsource.org/secure/attachment/19038/SpringJUnit4ParameterizedClassRunner.java" */ public class SpringJUnit4ParameterizedClassRunner extends Suite { /** * Annotation for a method which provides parameters to be injected into the * test class constructor by <code>Parameterized</code> */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public static @interface Parameters { } private class TestClassRunnerForParameters extends BlockJUnit4ClassRunner { private final int fParameterSetNumber; private final List<Object[]> fParameterList; private final Logger logger = LoggerFactory.getLogger(TestClassRunnerForParameters.class); private final TestContextManager testContextManager; /** * Constructs a new <code>SpringJUnit4ClassRunner</code> and initializes a * {@link TestContextManager} to provide Spring testing functionality to * standard JUnit tests. * * @param type the test class to be run * @see #createTestContextManager(Class) */ TestClassRunnerForParameters(Class<?> type, List<Object[]> parameterList, int i) throws InitializationError { super(type); if (logger.isDebugEnabled()) { logger.debug("SpringJUnit4ClassRunner constructor called with [" + type + "]."); } fParameterList= parameterList; fParameterSetNumber= i; this.testContextManager = createTestContextManager(type); } /** * Delegates to the parent implementation for creating the test instance and * then allows the {@link #getTestContextManager() TestContextManager} to * prepare the test instance before returning it. * * @see TestContextManager#prepareTestInstance(Object) */ @Override public Object createTest() throws Exception { Object testInstance = getTestClass().getOnlyConstructor().newInstance( computeParams()); getTestContextManager().prepareTestInstance(testInstance); return testInstance; } private Object[] computeParams() throws Exception { try { return fParameterList.get(fParameterSetNumber); } catch (ClassCastException e) { throw new Exception(String.format( "%s.%s() must return a Collection of arrays.", getTestClass().getName(), getParametersMethod( getTestClass()).getName())); } } @Override protected String getName() { return String.format("[%s]", fParameterSetNumber); } @Override protected String testName(final FrameworkMethod method) { return String.format("%s[%s]", method.getName(), fParameterSetNumber); } @Override protected void validateConstructor(List<Throwable> errors) { validateOnlyOneConstructor(errors); } @Override protected Statement classBlock(RunNotifier notifier) { return childrenInvoker(notifier); } /*SPRING*/ /** * Constructs a new <code>SpringJUnit4ClassRunner</code> and initializes a * {@link TestContextManager} to provide Spring testing functionality to * standard JUnit tests. * * @param clazz the test class to be run * @see #createTestContextManager(Class) */ // public TestClassRunnerForParameters(Class<?> clazz) throws InitializationError { // super(clazz); // if (logger.isDebugEnabled()) { // logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]."); // } // this.testContextManager = createTestContextManager(clazz); // } /** * Creates a new {@link TestContextManager} for the supplied test class and * the configured <em>default <code>ContextLoader</code> class name</em>. * Can be overridden by subclasses. * * @param clazz the test class to be managed * @see #getDefaultContextLoaderClassName(Class) */ protected TestContextManager createTestContextManager(Class<?> clazz) { return new TestContextManager(clazz); } /** * Get the {@link TestContextManager} associated with this runner. */ protected final TestContextManager getTestContextManager() { return this.testContextManager; } /** * Get the name of the default <code>ContextLoader</code> class to use for * the supplied test class. The named class will be used if the test class * does not explicitly declare a <code>ContextLoader</code> class via the * <code>@ContextConfiguration</code> annotation. * <p> * The default implementation returns <code>null</code>, thus implying use * of the <em>standard</em> default <code>ContextLoader</code> class name. * Can be overridden by subclasses. * </p> * * @param clazz the test class * @return <code>null</code> */ protected String getDefaultContextLoaderClassName(Class<?> clazz) { return null; } /** * Returns a description suitable for an ignored test class if the test is * disabled via <code>@IfProfileValue</code> at the class-level, and * otherwise delegates to the parent implementation. * * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) */ @Override public Description getDescription() { if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { return Description.createSuiteDescription(getTestClass().getJavaClass()); } return super.getDescription(); } /** * Check whether the test is enabled in the first place. This prevents * classes with a non-matching <code>@IfProfileValue</code> annotation * from running altogether, even skipping the execution of * <code>prepareTestInstance()</code> <code>TestExecutionListener</code> * methods. * * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) * @see org.springframework.test.annotation.IfProfileValue * @see org.springframework.test.context.TestExecutionListener */ @Override public void run(RunNotifier notifier) { if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { notifier.fireTestIgnored(getDescription()); return; } super.run(notifier); } /** * Wraps the {@link Statement} returned by the parent implementation with a * {@link RunBeforeTestClassCallbacks} statement, thus preserving the * default functionality but adding support for the Spring TestContext * Framework. * * @see RunBeforeTestClassCallbacks */ @Override protected Statement withBeforeClasses(Statement statement) { Statement junitBeforeClasses = super.withBeforeClasses(statement); return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager()); } /** * Wraps the {@link Statement} returned by the parent implementation with a * {@link RunAfterTestClassCallbacks} statement, thus preserving the default * functionality but adding support for the Spring TestContext Framework. * * @see RunAfterTestClassCallbacks */ @Override protected Statement withAfterClasses(Statement statement) { Statement junitAfterClasses = super.withAfterClasses(statement); return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager()); } /** * Performs the same logic as * {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)}, * except that tests are determined to be <em>ignored</em> by * {@link #isTestMethodIgnored(FrameworkMethod)}. */ @Override protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) { EachTestNotifier eachNotifier = springMakeNotifier(frameworkMethod, notifier); if (isTestMethodIgnored(frameworkMethod)) { eachNotifier.fireTestIgnored(); return; } eachNotifier.fireTestStarted(); try { methodBlock(frameworkMethod).evaluate(); } catch (AssumptionViolatedException e) { eachNotifier.addFailedAssumption(e); } catch (Throwable e) { eachNotifier.addFailure(e); } finally { eachNotifier.fireTestFinished(); } } /** * <code>springMakeNotifier()</code> is an exact copy of * {@link BlockJUnit4ClassRunner BlockJUnit4ClassRunner's} * <code>makeNotifier()</code> method, but we have decided to prefix it with * "spring" and keep it <code>private</code> in order to avoid the * compatibility clashes that were introduced in JUnit between versions 4.5, * 4.6, and 4.7. */ private EachTestNotifier springMakeNotifier(FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); return new EachTestNotifier(notifier, description); } /** * Augments the default JUnit behavior * {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with * potential repeats} of the entire execution chain. * <p> * Furthermore, support for timeouts has been moved down the execution chain * in order to include execution of {@link org.junit.Before @Before} * and {@link org.junit.After @After} methods within the timed * execution. Note that this differs from the default JUnit behavior of * executing <code>@Before</code> and <code>@After</code> methods * in the main thread while executing the actual test method in a separate * thread. Thus, the end effect is that <code>@Before</code> and * <code>@After</code> methods will be executed in the same thread as * the test method. As a consequence, JUnit-specified timeouts will work * fine in combination with Spring transactions. Note that JUnit-specific * timeouts still differ from Spring-specific timeouts in that the former * execute in a separate thread while the latter simply execute in the main * thread (like regular tests). * </p> * * @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement) * @see #withBefores(FrameworkMethod, Object, Statement) * @see #withAfters(FrameworkMethod, Object, Statement) * @see #withPotentialTimeout(FrameworkMethod, Object, Statement) * @see #withPotentialRepeat(FrameworkMethod, Object, Statement) */ @Override protected Statement methodBlock(FrameworkMethod frameworkMethod) { Object testInstance; try { testInstance = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(frameworkMethod, testInstance); statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement); statement = withRulesReflectively(frameworkMethod, testInstance, statement); statement = withBefores(frameworkMethod, testInstance, statement); statement = withAfters(frameworkMethod, testInstance, statement); statement = withPotentialRepeat(frameworkMethod, testInstance, statement); statement = withPotentialTimeout(frameworkMethod, testInstance, statement); return statement; } /** * Invokes JUnit 4.7's private <code>withRules()</code> method using * reflection. This is necessary for backwards compatibility with the JUnit * 4.5 and 4.6 implementations of {@link BlockJUnit4ClassRunner}. */ private Statement withRulesReflectively(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Method withRulesMethod = ReflectionUtils.findMethod(getClass(), "withRules", FrameworkMethod.class, Object.class, Statement.class); if (withRulesMethod != null) { // Original JUnit 4.7 code: // statement = withRules(frameworkMethod, testInstance, statement); ReflectionUtils.makeAccessible(withRulesMethod); statement = (Statement) ReflectionUtils.invokeMethod(withRulesMethod, this, frameworkMethod, testInstance, statement); } return statement; } /** * Returns <code>true</code> if {@link Ignore @Ignore} is present for * the supplied {@link FrameworkMethod test method} or if the test method is * disabled via <code>@IfProfileValue</code>. * * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class) */ protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) { Method method = frameworkMethod.getMethod(); return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method, getTestClass().getJavaClass())); } /** * Performs the same logic as * {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)} * except that the <em>expected exception</em> is retrieved using * {@link #getExpectedException(FrameworkMethod)}. */ // @Override // protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { // Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod); // return expectedException != null ? new ExpectException(next, expectedException) : next; // } /** * Get the <code>exception</code> that the supplied {@link FrameworkMethod * test method} is expected to throw. * <p> * Supports both Spring's {@link ExpectedException @ExpectedException(...)} * and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but * not both simultaneously. * </p> * * @return the expected exception, or <code>null</code> if none was * specified */ // protected Class<? extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) { // Test testAnnotation = frameworkMethod.getAnnotation(Test.class); // Class<? extends Throwable> junitExpectedException = testAnnotation != null // && testAnnotation.expected() != Test.None.class ? testAnnotation.expected() : null; // // ExpectedException expectedExAnn = frameworkMethod.getAnnotation(ExpectedException.class); // Class<? extends Throwable> springExpectedException = (expectedExAnn != null ? expectedExAnn.value() : null); // // if (springExpectedException != null && junitExpectedException != null) { // String msg = "Test method [" + frameworkMethod.getMethod() // + "] has been configured with Spring's @ExpectedException(" + springExpectedException.getName() // + ".class) and JUnit's @Test(expected=" + junitExpectedException.getName() // + ".class) annotations. " // + "Only one declaration of an 'expected exception' is permitted per test method."; // logger.error(msg); // throw new IllegalStateException(msg); // } // // return springExpectedException != null ? springExpectedException : junitExpectedException; // } /** * Supports both Spring's {@link Timed @Timed} and JUnit's * {@link Test#timeout() @Test(timeout=...)} annotations, but not both * simultaneously. Returns either a {@link SpringFailOnTimeout}, a * {@link FailOnTimeout}, or the unmodified, supplied {@link Statement} as * appropriate. * * @see #getSpringTimeout(FrameworkMethod) * @see #getJUnitTimeout(FrameworkMethod) */ @Override protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { Statement statement = null; long springTimeout = getSpringTimeout(frameworkMethod); long junitTimeout = getJUnitTimeout(frameworkMethod); if (springTimeout > 0 && junitTimeout > 0) { String msg = "Test method [" + frameworkMethod.getMethod() + "] has been configured with Spring's @Timed(millis=" + springTimeout + ") and JUnit's @Test(timeout=" + junitTimeout + ") annotations. Only one declaration of a 'timeout' is permitted per test method."; logger.error(msg); throw new IllegalStateException(msg); } else if (springTimeout > 0) { statement = new SpringFailOnTimeout(next, springTimeout); } else if (junitTimeout > 0) { statement = new FailOnTimeout(next, junitTimeout); } else { statement = next; } return statement; } /** * Retrieves the configured JUnit <code>timeout</code> from the {@link Test * @Test} annotation on the supplied {@link FrameworkMethod test * method}. * * @return the timeout, or <code>0</code> if none was specified. */ protected long getJUnitTimeout(FrameworkMethod frameworkMethod) { Test testAnnotation = frameworkMethod.getAnnotation(Test.class); return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0); } /** * Retrieves the configured Spring-specific <code>timeout</code> from the * {@link Timed @Timed} annotation on the supplied * {@link FrameworkMethod test method}. * * @return the timeout, or <code>0</code> if none was specified. */ protected long getSpringTimeout(FrameworkMethod frameworkMethod) { Timed timedAnnotation = frameworkMethod.getAnnotation(Timed.class); return (timedAnnotation != null && timedAnnotation.millis() > 0 ? timedAnnotation.millis() : 0); } /** * Wraps the {@link Statement} returned by the parent implementation with a * {@link RunBeforeTestMethodCallbacks} statement, thus preserving the * default functionality but adding support for the Spring TestContext * Framework. * * @see RunBeforeTestMethodCallbacks */ @Override protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement); return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(), getTestContextManager()); } /** * Wraps the {@link Statement} returned by the parent implementation with a * {@link RunAfterTestMethodCallbacks} statement, thus preserving the * default functionality but adding support for the Spring TestContext * Framework. * * @see RunAfterTestMethodCallbacks */ @Override protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement); return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(), getTestContextManager()); } /** * Supports Spring's {@link Repeat @Repeat} annotation by returning a * {@link SpringRepeat} statement initialized with the configured repeat * count or <code>1</code> if no repeat count is configured. * * @see SpringRepeat */ protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { Repeat repeatAnnotation = frameworkMethod.getAnnotation(Repeat.class); int repeat = (repeatAnnotation != null ? repeatAnnotation.value() : 1); return new SpringRepeat(next, frameworkMethod.getMethod(), repeat); } } private final ArrayList<Runner> runners= new ArrayList<Runner>(); /** * Only called reflectively. Do not use programmatically. */ public SpringJUnit4ParameterizedClassRunner(Class<?> klass) throws Throwable { super(klass, Collections.<Runner>emptyList()); List<Object[]> parametersList= getParametersList(getTestClass()); for (int i= 0; i < parametersList.size(); i++) runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(), parametersList, i)); } @Override protected List<Runner> getChildren() { return runners; } @SuppressWarnings("unchecked") private List<Object[]> getParametersList(TestClass klass) throws Throwable { return (List<Object[]>) getParametersMethod(klass).invokeExplosively( null); } private FrameworkMethod getParametersMethod(TestClass testClass) throws Exception { List<FrameworkMethod> methods= testClass .getAnnotatedMethods(Parameters.class); for (FrameworkMethod each : methods) { int modifiers= each.getMethod().getModifiers(); if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) return each; } throw new Exception("No public static parameters method on class " + testClass.getName()); } }