package org.jboss.byteman.contrib.bmunit;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import java.lang.reflect.Method;
/**
* Specialisation of the BlockJUnit4ClassRunner Runner class which can be attached to a text class
* using the @RunWith annotation. It ensures that Byteman rules are loaded and unloaded for tests
* which are annotated with an @Byteman annotation
*/
public class BMUnitRunner extends BlockJUnit4ClassRunner
{
private BMUnitConfig classConfigAnnotation;
BMScript classSingleScriptAnnotation;
BMScripts classMultiScriptAnnotation;
BMRules classMultiRuleAnnotation;
BMRule classSingleRuleAnnotation;
Class<?> testKlazz;
private final BMRunnerUtil BMRunnerUtil = new BMRunnerUtil();
/**
* Creates a BMUnitRunner to run test in {@code klass}
*
* @param klass idenitifies the Java class containing the tests
* @throws org.junit.runners.model.InitializationError
* if the test class is malformed.
*/
public BMUnitRunner(Class<?> klass) throws InitializationError {
super(klass);
testKlazz = getTestClass().getJavaClass();
classConfigAnnotation = testKlazz.getAnnotation(BMUnitConfig.class);
classSingleScriptAnnotation = testKlazz.getAnnotation(BMScript.class);
classMultiScriptAnnotation = testKlazz.getAnnotation(BMScripts.class);
classMultiRuleAnnotation = testKlazz.getAnnotation(BMRules.class);
classSingleRuleAnnotation = testKlazz.getAnnotation((BMRule.class));
if (classMultiRuleAnnotation != null && classSingleRuleAnnotation != null) {
throw new InitializationError("Use either BMRule or BMRules annotation but not both");
}
if (classMultiScriptAnnotation != null && classSingleScriptAnnotation != null) {
throw new InitializationError("Use either BMScript or BMScripts annotation but not both");
}
}
@Override
protected Statement childrenInvoker(RunNotifier notifier) {
Statement statement = super.childrenInvoker(notifier);
// n.b. we add the wrapper code in reverse order to the preferred order of loading
// as it works by wrapping around and so execution is in reverse order to wrapping
// i.e. this ensures that the class script rules get loaded before any rules specified
// using BMRule(s) annotations
statement = addClassSingleRuleLoader(statement, notifier);
statement = addClassMultiRuleLoader(statement, notifier);
statement = addClassSingleScriptLoader(statement, notifier);
statement = addClassMultiScriptLoader(statement, notifier);
statement = addClassConfigLoader(statement, notifier);
return statement;
}
protected Statement addClassConfigLoader(final Statement statement, RunNotifier notifier)
{
final RunNotifier fnotifier = notifier;
final Description description = Description.createTestDescription(testKlazz, getName(), classConfigAnnotation);
return new Statement() {
public void evaluate() throws Throwable {
try {
BMUnitConfigState.pushConfigurationState(classConfigAnnotation, testKlazz);
try {
statement.evaluate();
} finally {
try {
BMUnitConfigState.popConfigurationState(testKlazz);
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
};
}
protected Statement addClassSingleScriptLoader(final Statement statement, RunNotifier notifier)
{
if (classSingleScriptAnnotation == null) {
return statement;
} else {
final String name = BMRunnerUtil.computeBMScriptName(classSingleScriptAnnotation.value());
final RunNotifier fnotifier = notifier;
final Description description = Description.createTestDescription(testKlazz, getName(), classSingleScriptAnnotation);
final String loadDirectory = BMRunnerUtil.normaliseLoadDirectory(classSingleScriptAnnotation);
return new Statement() {
public void evaluate() throws Throwable {
try {
BMUnit.loadScriptFile(testKlazz, name, loadDirectory);
try {
statement.evaluate();
} finally {
try {
BMUnit.unloadScriptFile(testKlazz, name);
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
};
}
}
protected Statement addClassMultiScriptLoader(final Statement statement, RunNotifier notifier)
{
if (classMultiScriptAnnotation == null) {
return statement;
} else {
BMScript[] scriptAnnotations = classMultiScriptAnnotation.scripts();
Statement result = statement;
// note we iterate down here because we generate statements by wraparound
// which means the the outer statement gets executed first
for (int i = scriptAnnotations.length; i> 0; i--) {
BMScript scriptAnnotation= scriptAnnotations[i - 1];
final String name = BMRunnerUtil.computeBMScriptName(scriptAnnotation.value());
final RunNotifier fnotifier = notifier;
final Description description = Description.createTestDescription(testKlazz, getName(), scriptAnnotation);
final String loadDirectory = BMRunnerUtil.normaliseLoadDirectory(scriptAnnotation);
final Statement nextStatement = result;
result = new Statement() {
public void evaluate() throws Throwable {
try {
BMUnit.loadScriptFile(testKlazz, name, loadDirectory);
try {
nextStatement.evaluate();
} finally {
try {
BMUnit.unloadScriptFile(testKlazz, name);
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
};
}
return result;
}
}
protected Statement addClassMultiRuleLoader(final Statement statement, RunNotifier notifier)
{
if (classMultiRuleAnnotation == null) {
return statement;
} else {
final String scriptText = BMRunnerUtil.constructScriptText(classMultiRuleAnnotation.rules());
final RunNotifier fnotifier = notifier;
final Description description = Description.createTestDescription(testKlazz, getName(), classMultiRuleAnnotation);
return new Statement() {
public void evaluate() throws Throwable {
try {
BMUnit.loadScriptText(testKlazz, null, scriptText);
try {
statement.evaluate();
} finally {
try {
BMUnit.unloadScriptText(testKlazz, null);
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
};
}
}
protected Statement addClassSingleRuleLoader(final Statement statement, RunNotifier notifier)
{
if (classSingleRuleAnnotation == null) {
return statement;
} else {
final String scriptText = BMRunnerUtil.constructScriptText(new BMRule[]{classSingleRuleAnnotation});
final RunNotifier fnotifier = notifier;
final Description description = Description.createTestDescription(testKlazz, getName(), classSingleRuleAnnotation);
return new Statement() {
public void evaluate() throws Throwable {
try {
BMUnit.loadScriptText(testKlazz, null, scriptText);
try {
statement.evaluate();
} finally {
try {
BMUnit.unloadScriptText(testKlazz, null);
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
} catch (Exception e) {
fnotifier.fireTestFailure(new Failure(description, e));
}
}
};
}
}
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test)
{
Statement statement = super.methodInvoker(method, test);
// n.b. we add the wrapper code in reverse order to the preferred order of loading
// as it works by wrapping around and so execution is in reverse order to wrapping
// i.e. this ensures that the method script rules get loaded before any rules specified
// using BMRule(s) annotations
statement = addMethodSingleRuleLoader(statement, method);
statement = addMethodMultiRuleLoader(statement, method);
statement = addMethodSingleScriptLoader(statement, method);
statement = addMethodMultiScriptLoader(statement, method);
statement = addMethodConfigLoader(statement, method);
return statement;
}
protected Statement addMethodConfigLoader(final Statement statement, FrameworkMethod method)
{
final BMUnitConfig annotation = method.getAnnotation(BMUnitConfig.class);
final Method testMethod = method.getMethod();
return new Statement() {
public void evaluate() throws Throwable {
BMUnitConfigState.pushConfigurationState(annotation, testMethod);
try {
statement.evaluate();
} finally {
BMUnitConfigState.popConfigurationState(testMethod);
}
}
};
}
/**
* wrap the test method execution statement with the necessary
* load and unload calls if it has a BMScript annotation
* @param statement the statement to be evaluated
* @param method the method being tested
* @return the statement possibly wrapped with load and unload
* calls
*/
protected Statement addMethodSingleScriptLoader(final Statement statement, FrameworkMethod method)
{
BMScript annotation = method.getAnnotation(BMScript.class);
if (annotation == null) {
return statement;
} else {
// ensure we always have an actual name here instead of null because using
// null will clash with the name used for looking up rules when the clas
// has a BMRules annotation
final String name = BMRunnerUtil.computeBMScriptName(annotation.value(), method.getMethod());
final String loadDirectory = BMRunnerUtil.normaliseLoadDirectory(annotation);
return new Statement() {
public void evaluate() throws Throwable {
BMUnit.loadScriptFile(testKlazz, name, loadDirectory);
try {
statement.evaluate();
} finally {
BMUnit.unloadScriptFile(testKlazz, name);
}
}
};
}
}
/**
* wrap the test method execution statement with the necessary
* load and unload calls if it has a BMScripts annotation
* @param statement the statement to be evaluated
* @param method the method being tested
* @return the statement possibly wrapped with load and unload
* calls
*/
protected Statement addMethodMultiScriptLoader(final Statement statement, FrameworkMethod method)
{
BMScripts scriptsAnnotation = method.getAnnotation(BMScripts.class);
if (scriptsAnnotation == null) {
return statement;
} else {
BMScript[] scriptAnnotations = scriptsAnnotation.scripts();
Statement result = statement;
// note we iterate down here because we generate statements by wraparound
// which means the the outer statement gets executed first
for (int i = scriptAnnotations.length; i> 0; i--) {
BMScript scriptAnnotation = scriptAnnotations[i - 1];
final Statement nextStatement = result;
// ensure we always have an actual name here instead of null because using
// null will clash with the name used for looking up rules when the clas
// has a BMRules annotation
final String name = BMRunnerUtil.computeBMScriptName(scriptAnnotation.value(), method.getMethod());
final String loadDirectory = BMRunnerUtil.normaliseLoadDirectory(scriptAnnotation);
result = new Statement() {
public void evaluate() throws Throwable {
BMUnit.loadScriptFile(testKlazz, name, loadDirectory);
try {
nextStatement.evaluate();
} finally {
BMUnit.unloadScriptFile(testKlazz, name);
}
}
};
}
return result;
}
}
/**
* wrap the test method execution statement with the necessary
* load and unload calls if it has a BMRules annotation
* @param statement the statement to be evaluated
* @param method the method being tested
* @return the statement possibly wrapped with load and unload
* calls
*/
protected Statement addMethodMultiRuleLoader(final Statement statement, FrameworkMethod method)
{
BMRules annotation = method.getAnnotation(BMRules.class);
if (annotation == null) {
return statement;
} else {
final String name = method.getName();
final String script = BMRunnerUtil.constructScriptText(annotation.rules());
return new Statement() {
public void evaluate() throws Throwable {
BMUnit.loadScriptText(testKlazz, name, script);
try {
statement.evaluate();
} finally {
BMUnit.unloadScriptText(testKlazz, name);
}
}
};
}
}
/**
* wrap the test method execution statement with the necessary
* load and unload calls if it has a BMRule annotation
* @param statement the statement to be evaluated
* @param method the method being tested
* @return the statement possibly wrapped with load and unload
* calls
*/
protected Statement addMethodSingleRuleLoader(final Statement statement, FrameworkMethod method)
{
BMRule annotation = method.getAnnotation(BMRule.class);
if (annotation == null) {
return statement;
} else {
final String name = method.getName();
final String script = BMRunnerUtil.constructScriptText(new BMRule[]{annotation});
return new Statement() {
public void evaluate() throws Throwable {
BMUnit.loadScriptText(testKlazz, name, script);
try {
statement.evaluate();
} finally {
BMUnit.unloadScriptText(testKlazz, name);
}
}
};
}
}
}