/*==========================================================================*\ | $Id: MixRunner.java,v 1.3 2012/03/05 14:17:13 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2011 Virginia Tech | | This file is part of the Student-Library. | | The Student-Library is free software; you can redistribute it and/or | modify it under the terms of the GNU Lesser General Public License as | published by the Free Software Foundation; either version 3 of the | License, or (at your option) any later version. | | The Student-Library is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU Lesser General Public License for more details. | | You should have received a copy of the GNU Lesser General Public License | along with the Student-Library; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package student.testingsupport.junit4; import java.lang.reflect.Method; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.internal.runners.model.MultipleFailureException; import org.junit.internal.runners.model.ReflectiveCallable; import org.junit.internal.runners.statements.Fail; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; import org.junit.rules.MethodRule; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; //------------------------------------------------------------------------- /** * A custom JUnit runner which uses reflection to run both JUnit3 as well as * JUnit4 tests. The usefulness of this is that it can be used with a * {@code @RunWith} annotation in a parent class, and the resulting subclasses * can be written as if they are JUnit3 tests, but advanced users can use * annotations as well, and any functionality dictated by the superclass, for * instance {@code @Rule} annotations, will be applied to the children as * well. * * It also looks for JUnit3 setUp() and tearDown() methods and performs them * as if they are JUnit4 {@code @Before}s and {@code @After}s. * * @author Craig Estep * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.3 $, $Date: 2012/03/05 14:17:13 $ */ public class MixRunner extends BlockJUnit4ClassRunner { //~ Instance/static variables ............................................. private List<FrameworkMethod> befores = null; private boolean junit3methodsAdded = false; private boolean junit3aftersAdded = false; //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a JUnitMixRunner to run {@code klass} * * @param klass The test class to run * @throws InitializationError if the test class is malformed. */ public MixRunner(Class<?> klass) throws InitializationError { super(klass); } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Returns a {@link Statement}: run all non-overridden {@code @Before} * methods on this class and superclasses, as well as any JUnit3 setUp * methods, before running {@code next}; if any throws an Exception, stop * execution and pass the exception on. * * Note that in BlockJUnit4ClassRunner this method is deprecated. */ @Override protected Statement withBefores( FrameworkMethod method, Object target, Statement statement) { List<FrameworkMethod> annotatedBefores = getTestClass().getAnnotatedMethods(Before.class); if (befores != annotatedBefores) { befores = annotatedBefores; // FIXME: This code only finds setUp() if it is public, // when the inherited method is protected. Method[] methods = getTestClass().getJavaClass().getMethods(); for (Method m : methods) { // Need to check for correct signature // Need to ensure it isn't annotated as @Before already if (m.getName().equals("setUp")) { FrameworkMethod fm = new FrameworkMethod(m); // add at the end, so it will be executed last, after // all other @Before methods befores.add(fm); } } } return befores.isEmpty() ? statement : new RunBefores(statement, befores, target); } // ---------------------------------------------------------- /** * Returns a {@link Statement}: run all non-overridden {@code @After} * methods, as well as any JUnit3 tearDown methods, on this class and * superclasses before running {@code next}; all After methods are always * executed: exceptions thrown by previous steps are combined, if * necessary, with exceptions from After methods into a * {@link MultipleFailureException}. * * Note that in BlockJUnit4ClassRunner this method is deprecated. */ @Override protected Statement withAfters( FrameworkMethod method, Object target, Statement statement) { List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods(After.class); if (!junit3aftersAdded) { Method[] methods = getTestClass().getJavaClass().getMethods(); for (Method m : methods) { // Need to check for correct signature // Need to ensure it isn't annotated as @Before already if (m.getName().equals("tearDown")) { FrameworkMethod fm = new FrameworkMethod(m); // Add at position zero, so it will be executed first, // before all other @After methods afters.add(0, fm); } } junit3aftersAdded = true; } return afters.isEmpty() ? statement : new RunAfters(statement, afters, target); } // ---------------------------------------------------------- /** * Gathers all JUnit4 and JUnit3 test methods from this class and its * superclasses. * * @return the list of test methods to run. */ @Override protected List<FrameworkMethod> getChildren() { List<FrameworkMethod> children = super.computeTestMethods(); if (!junit3methodsAdded) { Method[] methods = getTestClass().getJavaClass().getMethods(); for (Method method : methods) { FrameworkMethod fm = new FrameworkMethod(method); if (method.getName().startsWith("test") && !children.contains(fm)) { children.add(fm); } } junit3methodsAdded = true; } return children; } // ---------------------------------------------------------- /** * Adds to {@code errors} a throwable for each problem noted with the * test class (available from {@link #getTestClass()}). Default * implementation adds an error for each method annotated with * {@code @BeforeClass} or {@code @AfterClass} that is not * {@code public static void} with no arguments. */ protected void collectInitializationErrors(List<Throwable> errors) { super.collectInitializationErrors(errors); for (int i = 0; i < errors.size(); i++) { if (errors.get(i).getMessage().equals("No runnable methods")) { errors.remove(i); break; } } } // ---------------------------------------------------------- @SuppressWarnings("deprecation") protected Statement methodBlock(FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); statement = new RunTestMethodWrapper(statement, test); return statement; } // ---------------------------------------------------------- /** * This method was declared private in the parent class, when it should * have been protected (sigh)--it takes a {@link Statement}, and decorates * it with all the {@link MethodRule}s in the test class. * @param method The test method itself. * @param target The instance of the test class, on which the method will * be called. * @param statement The decorated, executable representation of the method * call that has all supplementary behaviors added on. * @return A new statement that represents the incoming statement with * any method rules added to it. */ protected Statement withRules( FrameworkMethod method, Object target, Statement statement) { Statement result = statement; for (MethodRule each : getTestClass() .getAnnotatedFieldValues(target, Rule.class, MethodRule.class)) { result = each.apply(result, method, target); } return result; } }