/* * 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 net.grinder.scriptengine.groovy.junit; import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.List; import java.util.Map; import net.grinder.engine.process.JUnitThreadContextInitializer; import net.grinder.engine.process.JUnitThreadContextUpdater; import net.grinder.scriptengine.exception.AbstractExceptionProcessor; import net.grinder.scriptengine.groovy.GroovyExceptionProcessor; import net.grinder.scriptengine.groovy.junit.annotation.AfterProcess; import net.grinder.scriptengine.groovy.junit.annotation.AfterThread; import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess; import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread; import net.grinder.scriptengine.groovy.junit.annotation.Repeat; import net.grinder.scriptengine.groovy.junit.annotation.RunRate; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.internal.runners.model.MultipleFailureException; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; import org.junit.rules.MethodRule; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; /** * Grinder JUnit Runner. Grinder JUnit Runner is the custom {@link Runner} which lets the user can * run the Grinder script in the JUnit context. * * This runner has a little bit different characteristic from conventional JUnit test. * <ul> * <li>All Test annotated tests are executed with a single instance.</li> * <li>{@link BeforeProcess} and {@link AfterProcess} annotated methods are executed per each * process.</li> * <li>{@link BeforeThread} and {@link AfterThread} annotated methods are executed per each thread.</li> * <li>{@link Repeat} annotated * </ul> * * In addition, it contains a little different behavior from generic grinder test script. * <ul> * <li>It only initiates only 1 process and 1 thread.</li> * <li>Each <code>@test</code> annotated method are independent to run. So one failure from one * method doesn't block the other methods' runs</li> * </ul> * * @author JunHo Yoon * @author Mavlarn * @see BeforeProcess * @see BeforeThread * @see AfterThread * @see AfterProcess * @see Repeat * @since 1.0 */ public class GrinderRunner extends BlockJUnit4ClassRunner { private JUnitThreadContextInitializer threadContextInitializer; private JUnitThreadContextUpdater threadContextUpdater; private TestObjectFactory testTargetFactory; private PerThreadStatement finalPerThreadStatement; private AbstractExceptionProcessor exceptionProcessor = new GroovyExceptionProcessor(); private boolean enableRateRunner = true; private Map<FrameworkMethod, Statement> frameworkMethodCache = new HashMap<FrameworkMethod, Statement>(); /** * Constructor. * * @param klass klass * @throws InitializationError class initialization error. */ public GrinderRunner(Class<?> klass) throws InitializationError { super(klass); this.testTargetFactory = new TestObjectFactory() { @Override public TestClass getTestClass() { return GrinderRunner.this.getTestClass(); } @Override public Object createTest() throws Exception { return GrinderRunner.this.createTest(); } }; initializeGrinderContext(); } /** * Constructor. * * @param klass klass * @param runner runner class * @throws InitializationError class initialization error. */ public GrinderRunner(Class<?> klass, final Object runner) throws InitializationError { super(klass); this.testTargetFactory = new TestObjectFactory() { @Override public TestClass getTestClass() { return GrinderRunner.this.getTestClass(); } @Override public Object createTest() throws Exception { return runner; } }; initializeGrinderContext(); } protected void initializeGrinderContext() { this.threadContextInitializer = new JUnitThreadContextInitializer(); this.threadContextInitializer.initialize(); this.threadContextUpdater = threadContextInitializer.getThreadContextUpdater(); this.finalPerThreadStatement = new PerThreadStatement() { @Override void before() { attachWorker(); } @Override void after() { detachWorker(); } }; } @Override protected List<FrameworkMethod> getChildren() { return super.getChildren(); } @Override public void run(RunNotifier notifier) { registerRunNotifierListener(notifier); Description description = getDescription(); enableRateRunner = isRateRunnerEnabled(); EachTestNotifier testNotifier = new EachTestNotifier(notifier, description); try { Statement statement = classBlock(notifier); statement.evaluate(); } catch (AssumptionViolatedException e) { testNotifier.fireTestIgnored(); } catch (StoppedByUserException e) { throw e; } catch (Throwable e) { testNotifier.addFailure(e); } } /** * Check if the rate runner should be enabled. * * @return true if enabled; */ protected boolean isRateRunnerEnabled() { Description description = getDescription(); return description.testCount() > 1 && isRepeatRunnerEnabled(); } private boolean isRepeatRunnerEnabled() { Annotation[] annotations = getTestClass().getAnnotations(); boolean repeatAnnotation = false; for (Annotation each : annotations) { if (each.annotationType().equals(Repeat.class)) { repeatAnnotation = true; } } return repeatAnnotation; } @Override protected Statement classBlock(RunNotifier notifier) { Statement statement = childrenInvoker(notifier); statement = withRepeat(statement); statement = withBeforeThread(statement); statement = withBeforeProcess(statement); statement = withAfterThread(statement); statement = withAfterProcess(statement); return statement; } protected Statement withRepeat(Statement statement) { Annotation[] annotations = getTestClass().getAnnotations(); int repetition = 1; for (Annotation each : annotations) { if (each.annotationType().equals(Repeat.class)) { repetition = ((Repeat) each).value(); } } return new RepetitionStatement(statement, repetition, threadContextUpdater); } @SuppressWarnings("deprecation") protected Statement methodBlock(FrameworkMethod method) { Statement statement = frameworkMethodCache.get(method); if (statement != null) { return statement; } Object testObject = testTargetFactory.getTestObject(); statement = methodInvoker(method, testObject); statement = possiblyExpectingExceptions(method, testObject, statement); statement = withPotentialTimeout(method, testObject, statement); statement = withBefores(method, testObject, statement); statement = withAfters(method, testObject, statement); statement = withRules(method, testObject, statement); if (enableRateRunner) { statement = withRunRate(method, testObject, statement); } frameworkMethodCache.put(method, statement); return statement; } protected Statement withRunRate(FrameworkMethod method, @SuppressWarnings("UnusedParameters") Object target, Statement statement) { RunRate runRate = method.getAnnotation(RunRate.class); return runRate == null ? statement : new RunRateStatement(statement, runRate.value()); } private 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; } /** * Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this * class and superclasses before executing {@code statement}; if any throws an Exception, stop * execution and pass the exception on. * * @param statement statement * @return wrapped statement */ protected Statement withBeforeProcess(Statement statement) { TestClass testClass = getTestClass(); List<FrameworkMethod> befores = testClass.getAnnotatedMethods(BeforeProcess.class); befores.addAll(testClass.getAnnotatedMethods(BeforeClass.class)); return befores.isEmpty() ? statement : new RunBefores(statement, befores, null); } /** * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class * and superclasses before executing {@code statement}; all AfterClass methods are always * executed: exceptions thrown by previous steps are combined, if necessary, with exceptions * from AfterClass methods into a {@link MultipleFailureException}. * * @param statement statement * @return wrapped statement */ protected Statement withAfterProcess(Statement statement) { TestClass testClass = getTestClass(); List<FrameworkMethod> afters = testClass.getAnnotatedMethods(AfterProcess.class); afters.addAll(testClass.getAnnotatedMethods(AfterClass.class)); return afters.isEmpty() ? statement : new RunAfters(statement, afters, null); } protected Statement withAfterThread(Statement statement) { List<FrameworkMethod> afterThreads = getTestClass().getAnnotatedMethods(AfterThread.class); return new RunAfterThreads(statement, afterThreads, testTargetFactory, finalPerThreadStatement); } protected Statement withBeforeThread(Statement statement) { List<FrameworkMethod> beforeThreads = getTestClass().getAnnotatedMethods(BeforeThread.class); return new RunBeforeThreads(statement, beforeThreads, testTargetFactory, finalPerThreadStatement); } protected void registerRunNotifierListener(RunNotifier notifier) { notifier.addFirstListener(new RunListener() { @Override public void testStarted(Description description) throws Exception { } @Override public void testRunStarted(Description description) throws Exception { attachWorker(); } @Override public void testRunFinished(Result result) throws Exception { detachWorker(); } @Override public void testFailure(Failure failure) throws Exception { Throwable exception = failure.getException(); Throwable filtered = exceptionProcessor.filterException(exception); if (exception != filtered) { exception.initCause(filtered); } } }); } void attachWorker() { this.threadContextInitializer.attachWorkerThreadContext(); } void detachWorker() { this.threadContextInitializer.detachWorkerThreadContext(); } }