patterntesting.runtime.junit.SmokeRunner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of patterntesting-rt Show documentation
Show all versions of patterntesting-rt Show documentation
PatternTesting Runtime (patterntesting-rt) is the runtime component for
the PatternTesting framework. It provides the annotations and base classes
for the PatternTesting testing framework (e.g. patterntesting-check,
patterntesting-concurrent or patterntesting-exception) but can be also
used standalone for classpath monitoring or profiling.
It uses AOP and AspectJ to perform this feat.
/*
* $Id: SmokeRunner.java,v 1.30 2014/04/28 14:56:48 oboehm Exp $
*
* Copyright (c) 2010 by Oliver Boehm
*
* 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 orimplied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* (c)reated 29.03.2010 by oliver ([email protected])
*/
package patterntesting.runtime.junit;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import org.apache.commons.lang.StringUtils;
import org.junit.*;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.MultipleFailureException;
import org.junit.internal.runners.statements.*;
import org.junit.runner.Description;
import org.junit.runner.notification.*;
import org.junit.runners.*;
import org.junit.runners.model.*;
import org.slf4j.*;
import patterntesting.runtime.junit.internal.*;
/**
* This is the eXtended Runner for JUnit 4 which handles the SmokeTest and
* other annotations. In previous (intermediate) version it was called
* "XRunner". But because this may be a little bit confusing name it was
* renamed to "SmokeRunner" for the final version.
*
* It can be used together with the {@code @RunWith} annotation of JUnit 4.
*
* @author oliver
* @since 1.0 (29.03.2010)
*/
@SuppressWarnings("deprecation")
public class SmokeRunner extends ParentRunner {
//public class SmokeRunner extends BlockJUnit4ClassRunner {
private static final Logger log = LoggerFactory.getLogger(SmokeRunner.class);
private final SmokeFilter xfilter = new SmokeFilter();
/**
* Creates a SmokeRunner to run klass methods in parallel.
*
* @param klass the test class to run
* @throws InitializationError if the test class is malformed
*/
public SmokeRunner(final Class> klass) throws InitializationError {
super(klass);
}
/**
* The ParentRunner allows us no access to the filter. So we added this
* method here (e.g. needed by the ParallelRunner).
*
* @return the SmokeFilter
*/
protected final SmokeFilter getFilter() {
return this.xfilter;
}
// /**
// * If we extends from BlockJUnit4CLassRunner we must overwrite this method
// * here because we also support JUnit3 tests. Otherwise we would get an
// * InitializationExcption.
// */
// @Override
// protected void collectInitializationErrors(List errors) {
// }
/**
* Returns the methods that run tests. Default implementation
* returns all methods annotated with {@code @Test} on this
* class and superclasses that are not overridden.
*
* @return the methods that run tests
*/
@Override
protected List getChildren() {
TestClass testClass = this.getTestClass();
Class> javaClass = testClass.getJavaClass();
if (ProfiledStatement.isTestCaseClass(testClass)) {
return getJUnit3TestMethods(javaClass);
}
return testClass.getAnnotatedMethods(Test.class);
}
// /**
// * This method is private in ParentRunner so we copied it and adapted it
// * to our needs. It is not used by SmokeRunner but by the derived classes
// * ParallelRunner and ParallelProxyRunner (where this class here is the
// * common super class).
// *
// * @return the filtered children list
// */
// protected final List getFilteredChildren() {
// ArrayList filtered = new ArrayList();
// for (FrameworkMethod each : getChildren()) {
// if (this.getFilter().shouldRun(describeChild(each))) {
// filtered.add(each);
// }
// }
// return filtered;
// }
/**
* Create a Description
of a single test named
* name
in the class clazz
. Generally, this will
* be a leaf Description
. (see also
* {@link BlockJUnit4ClassRunner})
*
* @param child
* the name of the test (a method name for test annotated with
* {@link org.junit.Test})
* @return the description
* @see org.junit.runners.ParentRunner#describeChild(java.lang.Object)
*/
@Override
protected Description describeChild(final FrameworkMethod child) {
return Description.createTestDescription(getTestClass().getJavaClass(),
child.getName(), child.getAnnotations());
}
/**
* Checks the annotation of the method marked as "@BeforeClass" and add
* (or filters out) the beforeClass method (needed to solve.
*
* @param statement the statement(s) before
* @return the statement(s) after with the BeforeClass method
* {@link "https://sourceforge.net/tracker/?func=detail&aid=3034823&group_id=48833&atid=454317"}).
* @see org.junit.runners.ParentRunner#withBeforeClasses(org.junit.runners.model.Statement)
*/
@Override
protected Statement withBeforeClasses(final Statement statement) {
List filtered = filter(BeforeClass.class);
if (filtered.isEmpty()) {
return statement;
}
return new RunBefores(statement, filtered, null);
}
/**
* Checks the annotation of the method marked as "@AfterClass" and add
* (or filters out) the afterClass method (needed to solve.
*
* @param statement the statement(s) before
* @return the statement(s) after with the AfterClass method
* {@link "https://sourceforge.net/tracker/?func=detail&aid=3034823&group_id=48833&atid=454317"}).
* @see org.junit.runners.ParentRunner#withAfterClasses(org.junit.runners.model.Statement)
*/
@Override
protected Statement withAfterClasses(final Statement statement) {
List filtered = filter(AfterClass.class);
if (filtered.isEmpty()) {
return statement;
}
return new RunAfters(statement, filtered, null);
}
private List filter(final Class extends Annotation> annotationClass) {
List methods = this.getTestClass().getAnnotatedMethods(annotationClass);
return filter(methods);
}
private List filter(final List methods) {
List filtered = new ArrayList();
for (FrameworkMethod fm : methods) {
if (!this.xfilter.shouldBeIgnored(describeChild(fm))) {
filtered.add(fm);
} else if (log.isTraceEnabled()) {
log.trace(fm.getMethod() + " is filtered out");
}
}
return filtered;
}
// /**
// * Unfortunately sending a "fireTestIgnored" to the notifier does not work
// * as expected. Neither the test class is displayed as "ignored" in the
// * JUnit GUI of Eclipse nor the methods are displayed as "ignored".
// * But more critical is that the count of the method is not correct - e.g.
// * for the IntegrationTestClassTest with two methods the following is
// * shown in the GUI:
// *
// * Runs: 0/2
// *
// * That means: 0 methods of 2 methods are executed. But the correct display
// * should be:
// *
// * Runs: 0/0
// *
// * The workaround here is to send no "fireTestIgnored" for the class but for
// * the single methods. So the result is then "Runs: 2/2" - not nice but the
// * count is correct after running it.
// *
// * @param notifier the notifier
// * @see org.junit.runners.ParentRunner#run(org.junit.runner.notification.RunNotifier)
// */
// @Override
// public void run(final RunNotifier notifier) {
// if (this.shouldBeIgnored()) {
// EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription());
// testNotifier.fireTestIgnored();
// } else {
// super.run(notifier);
// }
// }
//
// /**
// * Here we handle the annotations like {@code @TestIntegration} or
// * {@code @Broken} for the whole class.
// *
// * @return true if test should be ignored
// */
// private boolean shouldBeIgnored() {
// TestClass testClass = this.getTestClass();
// if (runSmokeTests) {
// return !hasSmokeTests(testClass);
// }
// Annotation[] annotations = testClass.getAnnotations();
// for (int i = 0; i < annotations.length; i++) {
// Annotation a = annotations[i];
// if (a.annotationType().equals(IntegrationTest.class)
// && isIntegrationTest((IntegrationTest) a)) {
// return true;
// }
// }
// return this.xfilter.shouldBeIgnored(describeTestClass());
// }
//
// private final Description describeTestClass() {
// return Description.createSuiteDescription(getTestClass().getName(), getTestClass()
// .getAnnotations());
// }
//
// private boolean isIntegrationTest(final IntegrationTest it) {
// if (Environment.integrationTestEnabled) {
// log.trace("all tests including integration tests will be executed");
// return false;
// }
// log.info(this.getTestClass().getName() + " SKIPPED because "
// + it.value() + " (can be activated with '-D"
// + Environment.INTEGRATION_TEST + "')");
// return true;
// }
//
// /**
// * Looks for the given test class if it has the SmokeClass annotation or
// * one of its test methods have it.
// *
// * @param testClass the test class
// * @return true if smoke tests are found
// */
// private static boolean hasSmokeTests(final TestClass testClass) {
// if (testClass.getJavaClass().getAnnotation(SmokeTest.class) != null) {
// return true;
// }
// List smokeTests = testClass
// .getAnnotatedMethods(SmokeTest.class);
// return !smokeTests.isEmpty();
// }
/**
* Runs the test corresponding to {@code child}, which can be assumed to be
* an element of the list returned by {@link ParentRunner#getChildren()}.
* Subclasses are responsible for making sure that relevant test events are
* reported through {@code notifier}
*
* @param method the method
* @param notifier the notifier
*/
@Override
@SuppressWarnings("all")
protected void runChild(final FrameworkMethod method,
final RunNotifier notifier) {
Description description = describeChild(method);
try {
if (shouldBeIgnored(method)) {
notifier.fireTestIgnored(description);
return;
}
} catch (IllegalArgumentException iae) {
notifier.fireTestStarted(description);
fireTestAssumptionFailed(notifier, description, iae);
notifier.fireTestFinished(description);
return;
}
notifier.fireTestStarted(description);
Statement stmt = methodBlock(method);
try {
stmt.evaluate();
} catch (AssumptionViolatedException ex) {
fireTestAssumptionFailed(notifier, description, ex);
} catch (Throwable e) {
addFailure(notifier, e, description);
} finally {
logStatement(stmt);
notifier.fireTestFinished(description);
}
}
/**
* In JUnit 4.5 and newer we can use the fireTestAssumptionFailed(..)
* of the {@link RunNotifier} class. But JUnit 4.4 does not provide this
* method.
*
* We could map it to the {@link RunNotifier#fireTestFailure(Failure)}
* method - but this does not work for JUnit 4.5 (some internal JUnit tests
* will fail if you try that).
* We could compile with JUnit 4.5, run the tests with JUnit 4.4 and see
* what will happen. Perhaps we can catch the exception and call the
* {@link RunNotifier#fireTestFailure(Failure)} method.
* We could also give up because the architecture of JUnit has changed too
* much between 4.4 and 4.5 - this is, what we do now.
*
* @param notifier the notifier
* @param description the description
* @param ex the ex
* @since 1.2.20
*/
protected static void fireTestAssumptionFailed(final RunNotifier notifier,
final Description description, final Exception ex) {
notifier.fireTestAssumptionFailed(new Failure(description, ex));
//notifier.fireTestFailure(new Failure(description, ex));
}
/**
* We will give a subclass (like e.g. ParallelRunner) the chance to report
* the statement with its own logger.
*
* @param stmt the stmt to be logged
*/
protected void logStatement(final Statement stmt) {
log.info("{}", stmt);
}
/**
* Here we handle annotations like {@code @Ignore} where the execution of
* the test method should be ignored.
*
* @param method the test method
* @return true or false
*/
protected final boolean shouldBeIgnored(final FrameworkMethod method) {
Ignore ignore = method.getAnnotation(Ignore.class);
if (ignore != null) {
if (log.isDebugEnabled()) {
String reason = ignore.value();
if (StringUtils.isNotEmpty(reason)) {
reason = " (" + reason + ")";
}
log.debug(this.getTestClass().getName() + "."
+ method.getName() + " ignored" + reason);
}
return true;
}
return this.xfilter.shouldBeIgnored(describeChild(method));
}
/**
* Should be run.
*
* @param method the method
* @return true, if successful
*/
protected final boolean shouldBeRun(final FrameworkMethod method) {
return !this.xfilter.shouldBeIgnored(describeChild(method));
}
/**
* This method was inspired from an internal JUnit class
* (EachTestNotifier#addFailure(Throwable)).
*
* @param notifier the notifier
* @param targetException the target exception
* @param description the description
*/
protected final void addFailure(final RunNotifier notifier, final Throwable targetException,
final Description description) {
if (targetException instanceof MultipleFailureException) {
MultipleFailureException mfe = (MultipleFailureException) targetException;
for (Throwable each : mfe.getFailures()) {
addFailure(notifier, each, description);
}
return;
}
notifier.fireTestFailure(new Failure(description, targetException));
}
/**
* Creates a RunStatement for the given test method.
*
* @param method the test method
* @return a created RunStatement
*/
protected Statement methodBlock(final FrameworkMethod method) {
return new ProfiledStatement(this.getTestClass(), method);
}
/**
* Here we look after public void methods with "test" as prefix and with no
* arguments.
*
* NOTE: This method is public because it is also needed by
* patterntesting.concurrent.junit.JUnit3Executor
*
* @param testClass the test class
* @return a list of public methods starting with prefix "test"
*/
public static List getJUnit3TestMethods(final Class> testClass) {
List children = new ArrayList();
Method[] methods = testClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
int mod = method.getModifiers();
if (Modifier.isPrivate(mod) || Modifier.isStatic(mod)) {
continue;
}
if (method.getParameterTypes().length > 0) {
continue;
}
Class> returnType = method.getReturnType();
if (!returnType.toString().equalsIgnoreCase("void")) {
continue;
}
String name = method.getName();
if (name.startsWith("test")) {
if (Modifier.isPublic(mod)) {
FrameworkMethod child = new FrameworkMethod(method);
children.add(child);
} else {
log.warn(method + " isn't public");
}
}
}
return children;
}
}