/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.integration.internal;
import java.lang.reflect.*;
import javax.annotation.*;
import mockit.internal.*;
import mockit.internal.expectations.*;
import mockit.internal.injection.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
/**
* Base class for "test runner decorators", which provide integration between JMockit and specific
* test runners from JUnit and TestNG.
*/
public class TestRunnerDecorator
{
@Nullable private static SavePoint savePointForTestClass;
@Nullable private static SavePoint savePointForTest;
/**
* A "volatile boolean" is as good as a java.util.concurrent.atomic.AtomicBoolean here,
* since we only need the basic get/set operations.
*/
protected volatile boolean shouldPrepareForNextTest;
protected TestRunnerDecorator() { shouldPrepareForNextTest = true; }
protected final void updateTestClassState(@Nullable Object target, @Nonnull Class<?> testClass)
{
testClass = getActualTestClass(testClass);
try {
handleSwitchToNewTestClassIfApplicable(testClass);
if (target != null) {
handleMockFieldsForWholeTestClass(target);
}
}
catch (Error e) {
try {
rollbackForTestClass();
}
catch (Error err) {
StackTrace.filterStackTrace(err);
throw err;
}
throw e;
}
catch (RuntimeException e) {
rollbackForTestClass();
StackTrace.filterStackTrace(e);
throw e;
}
}
@Nonnull
private static Class<?> getActualTestClass(@Nonnull Class<?> testClass)
{
return testClass.isSynthetic() ? testClass.getSuperclass() : testClass;
}
private static void handleSwitchToNewTestClassIfApplicable(@Nonnull Class<?> testClass)
{
Class<?> currentTestClass = TestRun.getCurrentTestClass();
if (testClass != currentTestClass) {
if (currentTestClass == null) {
savePointForTestClass = new SavePoint();
}
else if (!currentTestClass.isAssignableFrom(testClass)) {
cleanUpMocksFromPreviousTestClass();
savePointForTestClass = new SavePoint();
}
TestRun.setCurrentTestClass(testClass);
}
}
public static void cleanUpMocksFromPreviousTestClass() { cleanUpMocks(true); }
protected static void cleanUpMocksFromPreviousTest() { cleanUpMocks(false); }
public static void cleanUpAllMocks()
{
cleanUpMocks(true);
TestRun.getMockClasses().discardStartupMocks();
}
private static void cleanUpMocks(boolean forTestClassAsWell)
{
discardTestLevelMockedTypes();
if (forTestClassAsWell) {
rollbackForTestClass();
}
clearFieldTypeRedefinitions();
}
private static void rollbackForTestClass()
{
SavePoint savePoint = savePointForTestClass;
if (savePoint != null) {
savePoint.rollback();
savePointForTestClass = null;
}
}
protected static void clearFieldTypeRedefinitions()
{
TypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
if (fieldTypeRedefinitions != null) {
fieldTypeRedefinitions.cleanUp();
TestRun.setFieldTypeRedefinitions(null);
}
}
protected static void prepareForNextTest()
{
if (savePointForTest == null) {
savePointForTest = new SavePoint();
}
TestRun.prepareForNextTest();
}
protected static void discardTestLevelMockedTypes()
{
SavePoint savePoint = savePointForTest;
if (savePoint != null) {
savePoint.rollback();
savePointForTest = null;
}
}
protected final void handleMockFieldsForWholeTestClass(@Nonnull Object target)
{
Class<?> testClass = getActualTestClass(target.getClass());
FieldTypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
if (fieldTypeRedefinitions == null) {
new ParameterNameExtractor().extractNames(testClass);
fieldTypeRedefinitions = new FieldTypeRedefinitions(testClass);
TestRun.setFieldTypeRedefinitions(fieldTypeRedefinitions);
TestedClassInstantiations testedClassInstantiations = new TestedClassInstantiations();
if (!testedClassInstantiations.findTestedAndInjectableFields(testClass)) {
testedClassInstantiations = null;
}
TestRun.setTestedClassInstantiations(testedClassInstantiations);
}
//noinspection ObjectEquality
if (target != TestRun.getCurrentTestInstance()) {
fieldTypeRedefinitions.assignNewInstancesToMockFields(target);
}
}
protected static void createInstancesForTestedFields(@Nonnull Object testClassInstance, boolean beforeSetup)
{
TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
if (testedClasses != null) {
TestRun.enterNoMockingZone();
try {
testedClasses.assignNewInstancesToTestedFields(testClassInstance, beforeSetup);
}
finally {
TestRun.exitNoMockingZone();
}
}
}
@Nullable
protected static Object[] createInstancesForAnnotatedParameters(
@Nonnull Object testClassInstance, @Nonnull Method testMethod, @Nullable Object[] parameterValues)
{
int numParameters = testMethod.getParameterTypes().length;
if (numParameters == 0) {
return null;
}
if (parameterValues == null || parameterValues.length != numParameters) {
parameterValues = new Object[numParameters];
}
TestMethod methodInfo = new TestMethod(testMethod, parameterValues);
TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
TestRun.enterNoMockingZone();
try {
ParameterTypeRedefinitions redefinitions = new ParameterTypeRedefinitions(methodInfo);
TestRun.getExecutingTest().setParameterRedefinitions(redefinitions);
if (testedClasses != null) {
testedClasses.createTestedParameters(testClassInstance, methodInfo);
}
}
finally {
TestRun.exitNoMockingZone();
}
return parameterValues;
}
protected static void concludeTestMethodExecution(
@Nonnull SavePoint savePoint, @Nullable Throwable thrownByTest, boolean thrownAsExpected)
throws Throwable
{
TestRun.enterNoMockingZone();
Error expectationsFailure = RecordAndReplayExecution.endCurrentReplayIfAny();
try {
clearTestedObjectsIfAny();
}
finally {
savePoint.rollback();
TestRun.exitNoMockingZone();
}
if (thrownByTest != null) {
if (expectationsFailure == null || !thrownAsExpected || isUnexpectedOrMissingInvocation(thrownByTest)) {
throw thrownByTest;
}
Throwable expectationsFailureCause = expectationsFailure.getCause();
if (expectationsFailureCause != null) {
expectationsFailureCause.initCause(thrownByTest);
}
}
if (expectationsFailure != null) {
throw expectationsFailure;
}
}
protected static void clearTestedObjectsIfAny()
{
TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
if (testedClasses != null) {
testedClasses.clearTestedObjects();
}
}
private static boolean isUnexpectedOrMissingInvocation(@Nonnull Throwable error)
{
Class<?> errorType = error.getClass();
return errorType == UnexpectedInvocation.class || errorType == MissingInvocation.class;
}
}