/* * Copyright 2014, The Sporting Exchange Limited * * 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 com.betfair.cougar.test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.junit.Ignore; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; 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.Parameterized; 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 java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; /** * Specialised runner that runs the tests a specified number of times each. Call setNumRuns from a method annotated with @BeforeClass * to define the number of runs to do, defaults to 1. */ public class ParameterizedMultiRunner extends Suite { private static Logger LOGGER = LoggerFactory.getLogger(ParameterizedMultiRunner.class); private static int numRuns = 1; public static void setNumRuns(int numRuns) { ParameterizedMultiRunner.numRuns = numRuns; } /** * 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; TestClassRunnerForParameters(Class<?> type, List<Object[]> parameterList, int i) throws InitializationError { super(type); fParameterList= parameterList; fParameterSetNumber= i; } @Override public Object createTest() throws Exception { return getTestClass().getOnlyConstructor().newInstance( computeParams()); } private Object[] computeParams() throws Exception { try { return fParameterList.get(fParameterSetNumber); } catch (ClassCastException e) { throw new Exception(String.format(//NOSONAR "%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 validateZeroArgConstructor(List<Throwable> errors) { // constructor can, nay, should have args. } @Override protected Statement classBlock(RunNotifier notifier) { return childrenInvoker(notifier); } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { EachTestNotifier eachNotifier= makeNotifier(method, notifier); if (method.getAnnotation(Ignore.class) != null) { eachNotifier.fireTestIgnored(); return; } eachNotifier.fireTestStarted(); try { for (int i=0; i<numRuns; i++) { if (LOGGER.isInfoEnabled()) { LOGGER.info("--Starting run "+(i+1)+" of "+method.getName()+"--"); } methodBlock(method).evaluate(); } } catch (AssumptionViolatedException e) { eachNotifier.addFailedAssumption(e); } catch (Throwable e) { eachNotifier.addFailure(e); } finally { eachNotifier.fireTestFinished(); } } protected EachTestNotifier makeNotifier(FrameworkMethod method, RunNotifier notifier) { Description description= describeChild(method); return new EachTestNotifier(notifier, description); } } private final ArrayList<Runner> runners= new ArrayList<Runner>(); /** * Only called reflectively. Do not use programmatically. */ public ParameterizedMultiRunner(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()); } }