![JAR search and dependency download from the Maven repository](/logo.png)
com.google.testing.threadtester.AnnotatedTestWrapper Maven / Gradle / Ivy
/*
* Copyright 2009 Weaver authors
*
* 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.google.testing.threadtester;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implementation of BaseTestWrapper designed for use with {@link
* AnnotatedTestRunner}.
*
* @author [email protected] (Alasdair Mackintosh)
*/
public class AnnotatedTestWrapper implements BaseTestWrapper {
/**
* Represents a single test case. Contains the various methods that define the
* test. E.g. the 'before' method, which is the method in the test class
* tagged with the ThreadedBefore annotation.
*/
// Visible for testing
class TestCase {
final String name;
final Method before;
final Method main;
final Method secondary;
final Method verification;
final Method after;
final Method target;
TestCase(String name, Method before, Method main, Method secondary, Method verification,
Method after, Method target) {
this.name = name;
this.before = before;
this.main = main;
this.secondary = secondary;
this.verification = verification;
this.after = after;
this.target = target;
}
}
/**
* Represents a set of test cases, as defined in a single test class.
*/
// Visible for testing
class TestCases extends ArrayList {
final Method beforeAllMethod;
final Method afterAllMethod;
TestCases(int size, Method beforeAllMethod, Method afterAllMethod) {
super(size);
this.beforeAllMethod = beforeAllMethod;
this.afterAllMethod = afterAllMethod;
}
}
/**
* Gets an annotation from a method, and verifies that if the given annotation
* type is non-null, then all of the other given annotation objects are null
*/
private T getUniqueAnnotation(Class annotationClass, Method method,
Annotation... others) {
T annotation = method.getAnnotation(annotationClass);
if (annotation != null) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException("Cannot apply " + annotation + " to static method");
}
for (Annotation other : others) {
if (other != null) {
throw new IllegalArgumentException("Cannot combine " + annotation + " with " + other +
" on method " + method);
}
}
}
return annotation;
}
/**
* Adds a name->method pair to the given map, but throws an exception if the name already exists.
*/
private void addUniqueMethod(Map map, String name, Method method,
Annotation annotation) {
if (map.containsKey(name)) {
throw new IllegalArgumentException("Cannot have multiple " + annotation +
" annotations with name " + name);
}
map.put(name, method);
}
/**
* Verifies the annotations on the test class, and returns a list of TestCase objects defining
* the tests in the test class.
*/
// Visible for testing
TestCases getTestCases(Class> testClass, List> instrumentedClasses) {
Map mainMethods = new HashMap();
Map secondaryMethods = new HashMap();
Map verifyMethods = new HashMap();
Method beforeMethod = null;
Method beforeAllMethod = null;
Method afterMethod = null;
Method afterAllMethod = null;
// Get a list of methods in the instrumentedClasses called by the test class.
Map methodMap = new CallChecker().getCallers(testClass, instrumentedClasses);
// Go through the methods in the test class, extracting and verifying the
// annotations.
Method[] methods = testClass.getMethods();
for (Method method : methods) {
ThreadedMain main = getUniqueAnnotation(ThreadedMain.class, method);
if (main != null) {
addUniqueMethod(mainMethods, main.name(), method, main);
}
ThreadedSecondary secondary = getUniqueAnnotation(ThreadedSecondary.class, method, main);
if (secondary != null) {
addUniqueMethod(secondaryMethods, secondary.name(), method, secondary);
}
ThreadedVerification verification =
getUniqueAnnotation(ThreadedVerification.class, method, main, secondary);
if (verification != null) {
addUniqueMethod(verifyMethods, verification.name(), method, verification);
}
ThreadedBefore before =
getUniqueAnnotation(ThreadedBefore.class, method, main, secondary, verification);
if (before != null) {
if (beforeMethod != null) {
throw new IllegalArgumentException("Only one " + before + " annotation allowed");
}
beforeMethod = method;
}
ThreadedAfter after =
getUniqueAnnotation(ThreadedAfter.class, method, main, secondary, verification, before);
if (after != null) {
if (afterMethod != null) {
throw new IllegalArgumentException("Only one " + after + " annotation allowed");
}
afterMethod = method;
}
ThreadedBeforeAll beforeAll = method.getAnnotation(ThreadedBeforeAll.class);
if (beforeAll != null) {
if (!Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException("ThreadedBeforeAll only allowed on static methods");
}
beforeAllMethod = method;
}
ThreadedAfterAll afterAll = method.getAnnotation(ThreadedAfterAll.class);
if (afterAll != null) {
if (!Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException("ThreadedAfterAll only allowed on static methods");
}
afterAllMethod = method;
}
}
if (mainMethods.size() == 0) {
throw new IllegalArgumentException("No methods tagged with @ThreadedMain");
}
if (beforeMethod == null) {
throw new IllegalArgumentException("No method tagged with @ThreadedBefore");
}
// Create a new set of test case objects based on the annotations.
//
TestCases testCases = new TestCases(mainMethods.size(), beforeAllMethod, afterAllMethod);
for (String name : mainMethods.keySet()) {
Method secondaryMethod = secondaryMethods.get(name);
if (secondaryMethod == null) {
throw new IllegalArgumentException("No secondary method for test \"" + name + "\"");
}
secondaryMethods.remove(name);
// Find the target method invoked by the main test method, using the map
// from the CallChecker.
Method targetMethod = methodMap.get(mainMethods.get(name));
if (targetMethod == null) {
throw new IllegalArgumentException("Method @ThreadedMain(\"" + name +
"\") does not call a method in an instrumented class");
}
// Create a new test case. The verification method is optional, so we
// don't test for it.
testCases.add(new TestCase(name, beforeMethod, mainMethods.get(name), secondaryMethod,
verifyMethods.get(name), afterMethod, targetMethod));
}
// After creating the test cases, we should have removed all of the
// secondary methods from the list. If any are left, we don't have
// corresponding main methods.
if (secondaryMethods.size() > 0) {
for (String name : secondaryMethods.keySet()) {
throw new IllegalArgumentException("Secondary method for test case \"" + name +
"\" has no main method");
}
}
return testCases;
}
@Override
public void runTests(Class> testClass, List instrumentedClassNames) throws Exception {
Object mainObject = null;
List> instrumentedClasses = new ArrayList>(instrumentedClassNames.size());
for (String name : instrumentedClassNames) {
instrumentedClasses.add(Class.forName(name));
}
// Get the test cases defined by the annotations
TestCases testCases = getTestCases(testClass, instrumentedClasses);
// And run the generated test cases, bracketed by the before/after methods.
if (testCases.beforeAllMethod != null) {
MethodCaller.invoke(testCases.beforeAllMethod, null);
}
runTestCases(testClass, testCases);
if (testCases.afterAllMethod != null) {
MethodCaller.invoke(testCases.afterAllMethod, null);
}
}
private void runTestCases(Class> testClass, List testCases) {
Options.debugPrint("Running tests for class %s\n", testClass);
for (TestCase testCase : testCases) {
Options.debugPrint(" test case %s has %s, %s, %s\n", testCase.name, testCase.main.getName(),
testCase.secondary.getName(),
testCase.verification == null ? "null" : testCase.verification.getName());
MainTestCaseRunner main =
new MainTestCaseRunner(testClass, testCase);
SecondaryTestCaseRunner secondary = new SecondaryTestCaseRunner();
RunResult result = InterleavedRunner.interleave(main, secondary);
result.throwExceptionsIfAny();
}
}
/**
* Implementation of MainRunnable that runs the main thread of an
* annotated test case. It does this by creating a new instance of the test
* class, and invoking the methods defined in the TestCase on that new
* instance. This runner is passed to an InterleavedRunner, and hence will be
* invoked several times as the main and secondary threads are interleaved.
*/
private class MainTestCaseRunner implements MainRunnable
© 2015 - 2025 Weber Informatics LLC | Privacy Policy