patterntesting.concurrent.junit.ParallelRunner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of patterntesting-concurrent Show documentation
Show all versions of patterntesting-concurrent Show documentation
PatternTesting Concurrent (patterntesting-concurrent) is a collection
of useful thread aspects. It has support for testing, for
sychnronization and for concurrent programming.
Some of the ideas used in this library comes from reading
Brian Goetz's book "Java Concurrency in Practice".
/*
* $Id: ParallelRunner.java,v 1.15 2010/07/21 16:29:45 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 16.03.2010 by oliver ([email protected])
*/
package patterntesting.concurrent.junit;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import org.apache.commons.logging.*;
import org.junit.*;
import org.junit.Test.None;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.*;
import patterntesting.annotation.check.runtime.MayReturnNull;
import patterntesting.runtime.junit.*;
import patterntesting.runtime.junit.internal.JUnitHelper;
/**
* Implements the JUnit 4 standard test case class model, as defined by the
* annotations in the org.junit package. It will run the test methods in
* parallel.
*
* It also supports JUnit 3 test cases now. I.e. you can add
* {@code @RunWith(ParallelRunner.class)} also in front of a JUnit3 TestCase.
*
* @author oliver
* @since 1.0 (16.03.2010)
*/
public final class ParallelRunner extends SmokeRunner {
private static final Log log = LogFactory.getLog(ParallelRunner.class);
private final Map results = new HashMap();
private final Executor executor = Executors.newCachedThreadPool();
/**
* Class for the result and the used FutureTask.
*/
private static class Result {
/**
* @param method the method
*/
Result(final FrameworkMethod method) {
this.method = method;
}
/** The called method. */
FrameworkMethod method;
/** The (future) result. */
FutureTask future;
}
/**
* Creates a ParallelRunner to run klass methods in parallel.
*
* @param klass the test class to run
* @throws InitializationError if the test class is malformed
*/
public ParallelRunner(final Class> klass) throws InitializationError {
super(klass);
}
/**
* Returns a {@link Statement}: Call {@link #runChild(FrameworkMethod, RunNotifier)}
* on each object returned by {@link #getChildren()} (subject to any imposed
* filter and sort).
* @see org.junit.runners.ParentRunner#childrenInvoker(org.junit.runner.notification.RunNotifier)
*/
@Override
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
/**
* In contradiction to the original method from ParentRunner no filtering
* is done. And the tests are recorded at the beginning. Later in
* runChild(..) only the recorded result will be returned.
*
* @param notifier the RunNotifier
*/
private void runChildren(final RunNotifier notifier) {
this.recordResults();
for (final FrameworkMethod each : getFilteredChildren()) {
Runnable r = (new Runnable() {
public void run() {
runChild(each, notifier);
}
});
r.run();
}
}
/**
* We want to see to log the statement with our logger.
*
* @param stmt the stmt to be logged
* @see SmokeRunner#logStatement(org.junit.runners.model.Statement)
* @see SmokeRunner#runChild(FrameworkMethod, RunNotifier)
*/
@Override
protected void logStatement(Statement stmt) {
log.info(stmt);
}
/**
* This method is private in ParentRunner so we copied those parts we
* need.
*
* @return the filtered children list
*/
private List getFilteredChildren() {
ArrayList filtered = new ArrayList();
for (FrameworkMethod each : getChildren()) {
if (this.getFilter().shouldRun(describeChild(each))) {
filtered.add(each);
}
}
return filtered;
}
/**
* We will return here the statement recorded earlier.
* (see also BlockJUnit4ClassRunner#methodBlock(FrameworkMethod))
*
* @param method the test method to be called
* @return the (recorded) result of this test method
*/
@Override
protected Statement methodBlock(final FrameworkMethod method) {
Result result = results.get(method);
if ((result == null) || (result.future == null)) {
return new RecordedStatement(this.getTestClass(), method,
new AssertionError(method.getName()
+ " not found in recorded results"));
}
try {
return result.future.get();
} catch (InterruptedException e) {
log.info(method.getName() + " was interrupted", e);
return new RecordedStatement(this.getTestClass(), method, e);
} catch (ExecutionException e) {
log.info("can't execute " + method.getName(), e);
return new RecordedStatement(this.getTestClass(), method, e);
}
}
/**
* We will append the sign for parallel ("||") at the end of the
* description as indication that the tests will run in parallel.
*
* @return name with "||" appended
* @see org.junit.runners.ParentRunner#getDescription()
*/
@Override
protected String getName() {
String name = super.getName();
return name + "||";
}
///// JUnit3 support section //////////////////////////////////////////
@MayReturnNull
static FrameworkMethod getFrameworkMethod(final TestClass testClass,
final String name) {
return JUnitHelper.getFrameworkMethod(testClass.getJavaClass(), name);
}
private void invoke(final TestClass testClass, final String methodName,
final Object target, final RecordedStatement statement) {
FrameworkMethod method = getFrameworkMethod(testClass, methodName);
if (method != null) {
invoke(method, target, statement);
}
}
///// concurrency section /////////////////////////////////////////////
/**
* Here we will start the tests in parallel and record the results.
*/
private void recordResults() {
List testMethods = this.getChildren();
if (log.isTraceEnabled()) {
log.trace(testMethods.size() + " test methods found: "
+ testMethods);
}
for (FrameworkMethod method : testMethods) {
Result result = new Result(method);
results.put(method, result);
triggerTest(result);
}
}
/**
* Here we trigger the test only and store the result (a Statement) in a
* "Future" object.
*
* @param result this contains the JUnit method
*/
@MayReturnNull
private void triggerTest(final Result result) {
Callable callable = new Callable() {
public Statement call() {
return invokeTest(result.method);
}
};
result.future = new FutureTask(callable);
executor.execute(result.future);
}
/**
* Before we can start the given test method we must invoke all setUp
* methods. This will be done here. After the test method was executed
* we have to call the tearDown methods. This will be also done here.
*
* @param method the test method
* @return the RecordedStatement
*/
private Statement invokeTest(final FrameworkMethod method) {
RecordedStatement statement = new RecordedStatement(
this.getTestClass(), method);
if (this.shouldBeIgnored(method)) {
// if (method.getAnnotation(Ignore.class) != null) {
// if (log.isTraceEnabled()) {
// log.trace(method.getName() + "() will be ignored");
// }
return statement;
}
try {
Object target = this.getTestClass().getOnlyConstructor().newInstance();
statement.startTimer();
invokeBefores(target, statement);
statement.startTestTimer();
if (statement.success()) {
Test annotation = method.getAnnotation(Test.class);
if (annotation == null) {
this.invoke(method, target, statement);
} else {
this.invoke(method, target, annotation, statement);
}
}
statement.startAftersTimer();
invokeAfters(target, statement);
statement.endTimer();
return statement;
} catch (InstantiationException e) {
log.warn("can't instantiate " + this.getTestClass(), e);
return new RecordedStatement(this.getTestClass(), method, e);
} catch (IllegalAccessException e) {
log.warn("can't access " + method.getName(), e);
return new RecordedStatement(this.getTestClass(), method, e);
} catch (InvocationTargetException e) {
log.warn(method.getName() + " failed", e.getTargetException());
return new RecordedStatement(this.getTestClass(), method, e
.getTargetException());
}
}
/**
* Here we invoke all setUp() methods.
* @param target the instantiated JUnit test
* @param statement the recorded statement
*/
protected void invokeBefores(final Object target,
final RecordedStatement statement) {
TestClass testClass = this.getTestClass();
if (isTestCaseClass(testClass)) {
invoke(testClass, "setUp", target, statement);
} else {
List befores = testClass.getAnnotatedMethods(Before.class);
invoke(befores, target, statement);
}
}
/**
* Here we invoke all tearDown() methods.
* @param target the instantiated JUnit test
* @param stmt the recorded statement
*/
protected void invokeAfters(final Object target, final RecordedStatement stmt) {
TestClass testClass = this.getTestClass();
if (isTestCaseClass(testClass)) {
invoke(testClass, "tearDown", target, stmt);
} else {
List befores = testClass.getAnnotatedMethods(After.class);
invoke(befores, target, stmt);
}
}
private void invoke(final List frameworkMethod,
final Object target, final RecordedStatement stmt) {
for (FrameworkMethod before : frameworkMethod) {
invoke(before, target, stmt);
if (stmt.failed()) {
return;
}
}
}
private void invoke(final FrameworkMethod frameworkMethod,
final Object target, final RecordedStatement stmt) {
Method method = frameworkMethod.getMethod();
try {
method.setAccessible(true);
method.invoke(target);
} catch (IllegalAccessException e) {
stmt.setThrown(getAssertionErrorFor(method, e));
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t != null) {
stmt.setThrown(t);
} else {
stmt.setThrown(getAssertionErrorFor(method, e));
}
}
}
/**
* If the test was annotated with {@code expected=RuntimeException.class}
* we must look in the calculated statement if this expected exception was
* thrown. If yes the statement should be marked as "success".
*
* @param method the FrameworkMethod
* @param target the instantiated test
* @param test the Test annotation with a possibly expected value
* @param stmt the recorded statement
*/
private void invoke(final FrameworkMethod method, final Object target,
final Test test, final RecordedStatement stmt) {
invoke(method, target, stmt);
Class extends Throwable> expected = test.expected();
if ((expected != null) && (expected != None.class)) {
stmt.setExpected(expected);
}
}
private AssertionError getAssertionErrorFor(final Method method, final Throwable t) {
String detailedMessage = "invoke of " + this.getTestClass() + "."
+ method + "() failed\n" + t;
return new AssertionError(detailedMessage);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy