mockit.internal.expectations.RecordAndReplayExecution Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmockit Show documentation
Show all versions of jmockit Show documentation
JMockit is a Java toolkit for developer (unit/integration) testing.
It contains mocking APIs and other tools, supporting both JUnit and TestNG.
The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested
in isolation from selected dependencies.
The newest version!
/*
* Copyright (c) 2006-2014 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.expectations;
import java.util.*;
import java.util.concurrent.locks.*;
import org.jetbrains.annotations.*;
import mockit.*;
import mockit.internal.expectations.invocation.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.startup.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
public final class RecordAndReplayExecution
{
public static final ReentrantLock RECORD_OR_REPLAY_LOCK = new ReentrantLock();
public static final ReentrantLock TEST_ONLY_PHASE_LOCK = new ReentrantLock();
@Nullable private final FieldTypeRedefinitions redefinitions;
@Nullable private final DynamicPartialMocking dynamicPartialMocking;
@NotNull final PhasedExecutionState executionState;
final int lastExpectationIndexInPreviousReplayPhase;
@NotNull final FailureState failureState;
@Nullable private RecordPhase recordPhase;
@Nullable private ReplayPhase replayPhase;
@Nullable private BaseVerificationPhase verificationPhase;
public RecordAndReplayExecution()
{
validateRecordingContext(false);
executionState = new PhasedExecutionState();
lastExpectationIndexInPreviousReplayPhase = 0;
redefinitions = null;
dynamicPartialMocking = null;
discoverMockedTypesAndInstancesForMatchingOnInstance();
failureState = new FailureState();
replayPhase = new ReplayPhase(this);
}
private int getLastExpectationIndexInPreviousReplayPhase()
{
return replayPhase == null ? -1 : replayPhase.currentStrictExpectationIndex;
}
private void validateRecordingContext(boolean recordingExpectations)
{
if (TestRun.getSharedFieldTypeRedefinitions() == null) {
String msg;
if (Startup.wasInitializedOnDemand()) {
msg =
"JMockit wasn't properly initialized; check that jmockit.jar precedes junit.jar in the classpath " +
"(if using JUnit; if not, check the documentation)";
}
else if (recordingExpectations) {
msg = "Invalid place to record expectations";
}
else {
msg = "Invalid place to verify expectations";
}
IllegalStateException failure = new IllegalStateException(msg);
StackTrace.filterStackTrace(failure);
throw failure;
}
}
public RecordAndReplayExecution(
@NotNull Expectations targetObject, @Nullable Object... classesOrInstancesToBePartiallyMocked)
{
validateRecordingContext(true);
TestRun.enterNoMockingZone();
ExecutingTest executingTest = TestRun.getExecutingTest();
executingTest.setShouldIgnoreMockingCallbacks(true);
try {
RecordAndReplayExecution previous = executingTest.getRecordAndReplay();
if (previous == null) {
executionState = new PhasedExecutionState();
lastExpectationIndexInPreviousReplayPhase = 0;
}
else {
executionState = previous.executionState;
lastExpectationIndexInPreviousReplayPhase = previous.getLastExpectationIndexInPreviousReplayPhase();
}
failureState = new FailureState();
boolean nonStrict = targetObject instanceof NonStrictExpectations;
recordPhase = new RecordPhase(this, nonStrict);
executingTest.setRecordAndReplay(this);
redefinitions = redefineFieldTypes(targetObject);
dynamicPartialMocking = applyDynamicPartialMocking(nonStrict, classesOrInstancesToBePartiallyMocked);
discoverMockedTypesAndInstancesForMatchingOnInstance();
//noinspection LockAcquiredButNotSafelyReleased
TEST_ONLY_PHASE_LOCK.lock();
}
catch (RuntimeException e) {
executingTest.setRecordAndReplay(null);
throw e;
}
finally {
executingTest.setShouldIgnoreMockingCallbacks(false);
TestRun.exitNoMockingZone();
}
}
@Nullable private FieldTypeRedefinitions redefineFieldTypes(@NotNull Expectations targetObject)
{
LocalFieldTypeRedefinitions redefs = new LocalFieldTypeRedefinitions(targetObject, this);
//noinspection CatchGenericClass
try {
redefs.redefineLocalFieldTypes();
return redefs.getTypesRedefined() == 0 ? null : redefs;
}
catch (Error e) {
redefs.cleanUp();
StackTrace.filterStackTrace(e);
throw e;
}
catch (RuntimeException e) {
redefs.cleanUp();
StackTrace.filterStackTrace(e);
throw e;
}
}
private void discoverMockedTypesAndInstancesForMatchingOnInstance()
{
SharedFieldTypeRedefinitions fieldTypeRedefinitions = TestRun.getSharedFieldTypeRedefinitions();
assert fieldTypeRedefinitions != null;
List> fields = fieldTypeRedefinitions.getTargetClasses();
List> targetClasses = new ArrayList>(fields);
if (redefinitions != null) {
targetClasses.addAll(redefinitions.getTargetClasses());
}
ParameterTypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterTypeRedefinitions();
if (paramTypeRedefinitions != null) {
targetClasses.addAll(paramTypeRedefinitions.getTargetClasses());
}
executionState.discoverMockedTypesToMatchOnInstances(targetClasses);
if (dynamicPartialMocking != null) {
executionState.setDynamicMockInstancesToMatch(dynamicPartialMocking.targetInstances);
}
}
public void addMockedTypeToMatchOnInstance(@NotNull Class> mockedType)
{
executionState.addMockedTypeToMatchOnInstance(mockedType);
}
@Nullable private DynamicPartialMocking applyDynamicPartialMocking(
boolean nonStrict, @Nullable Object... classesOrInstances)
{
if (classesOrInstances == null || classesOrInstances.length == 0) {
return null;
}
DynamicPartialMocking mocking = new DynamicPartialMocking(nonStrict);
mocking.redefineTypes(classesOrInstances);
return mocking;
}
@NotNull public RecordPhase getRecordPhase()
{
if (recordPhase == null) {
throw new IllegalStateException("Not in the recording phase");
}
return recordPhase;
}
@Nullable Error getErrorThrown() { return failureState.getErrorThrown(); }
void setErrorThrown(@Nullable Error error) { failureState.setErrorThrown(error); }
/**
* Only to be called from generated bytecode or from the Mocking Bridge.
*/
@Nullable
public static Object recordOrReplay(
@Nullable Object mock, int mockAccess, @NotNull String classDesc, @NotNull String mockDesc,
@Nullable String genericSignature, @Nullable String exceptions, int executionMode, @NotNull Object... args)
throws Throwable
{
if (
RECORD_OR_REPLAY_LOCK.isHeldByCurrentThread() ||
TEST_ONLY_PHASE_LOCK.isLocked() && !TEST_ONLY_PHASE_LOCK.isHeldByCurrentThread()
) {
// This occurs if called from a custom argument matching method, in a call to an overridden Object method
// (equals, hashCode, toString), during static initialization of a mocked class which calls another mocked
// method, or from a different thread during a recording/verification.
return defaultReturnValue(mock, classDesc, mockDesc, genericSignature, executionMode, args);
}
ExecutingTest executingTest = TestRun.getExecutingTest();
if (executingTest.isShouldIgnoreMockingCallbacks()) {
// This occurs when called from a reentrant delegate method, or during static initialization of a mocked class
// being instantiated for a local mock field.
return defaultReturnValue(executingTest, mock, classDesc, mockDesc, genericSignature, executionMode, args);
}
else if (executingTest.isProceedingIntoRealImplementation()) {
if (executionMode == 0) {
throw new UnsupportedOperationException(
"Cannot proceed into method unless mocked type is injectable or dynamic");
}
return Void.class;
}
else if (mock != null && executionMode == 3 && !TestRun.mockFixture().isInstanceOfMockedClass(mock)) {
return Void.class;
}
executingTest.registerAdditionalMocksFromFinalLocalMockFieldsIfAny();
if (executionMode == 2 && (mock == null || !executingTest.isInjectableMock(mock))) {
return Void.class;
}
RECORD_OR_REPLAY_LOCK.lock();
try {
RecordAndReplayExecution instance = TestRun.getRecordAndReplayForRunningTest(true);
if (mock != null && mockDesc.startsWith("") && handleCallToConstructor(instance, mock, classDesc)) {
return
executionMode == 0 || executionMode == 3 ||
executionMode == 1 && !inReplayPhase(instance) ||
executingTest.isInjectableMock(mock) ? null : Void.class;
}
if (instance == null) {
// This occurs when a constructor of the mocked class is called in a mock field assignment expression,
// during initialization of a mocked class, or during the restoration of mocked classes between tests.
return defaultReturnValue(executingTest, mock, classDesc, mockDesc, genericSignature, executionMode, args);
}
Phase currentPhase = instance.getCurrentPhase();
instance.failureState.clearErrorThrown();
boolean withRealImpl = executionMode == 1;
Object result =
currentPhase.handleInvocation(
mock, mockAccess, classDesc, mockDesc, genericSignature, exceptions, withRealImpl, args);
instance.failureState.reportErrorThrownIfAny();
return result;
}
finally {
RECORD_OR_REPLAY_LOCK.unlock();
}
}
@Nullable
public static Object defaultReturnValue(
@Nullable Object mock, @NotNull String classDesc, @NotNull String nameAndDesc,
@Nullable String genericSignature, int executionMode, @NotNull Object[] args)
{
if (executionMode == 1) return Void.class;
if (mock != null) {
Object rv = ObjectMethods.evaluateOverride(mock, nameAndDesc, args);
if (rv != null) {
return rv;
}
}
String returnTypeDesc = DefaultValues.getReturnTypeDesc(nameAndDesc);
Object cascadedInstance = MockedTypeCascade.getMock(classDesc, mock, returnTypeDesc, genericSignature);
if (cascadedInstance != null) return cascadedInstance;
return executionMode == 0 ? DefaultValues.computeForReturnType(nameAndDesc) : Void.class;
}
@Nullable
private static Object defaultReturnValue(
@NotNull ExecutingTest executingTest, @Nullable Object mock,
@NotNull String classDesc, @NotNull String nameAndDesc, @Nullable String genericSignature,
int executionMode, @NotNull Object[] args) throws Throwable
{
RecordAndReplayExecution execution = executingTest.getCurrentRecordAndReplay();
if (execution != null) {
Expectation recordedExpectation =
execution.executionState.findNonStrictExpectation(mock, classDesc, nameAndDesc, args);
if (recordedExpectation != null) {
return recordedExpectation.produceResult(mock, args);
}
}
return defaultReturnValue(mock, classDesc, nameAndDesc, genericSignature, executionMode, args);
}
private static boolean inReplayPhase(@Nullable RecordAndReplayExecution instance)
{
return instance == null || instance.replayPhase != null;
}
private static boolean handleCallToConstructor(
@Nullable RecordAndReplayExecution instance, @NotNull Object mock, @NotNull String classDesc)
{
if (TestRun.getCurrentTestInstance() != null && inReplayPhase(instance)) {
FieldTypeRedefinitions fieldTypeRedefs = instance == null ? null : instance.redefinitions;
if (fieldTypeRedefs != null && fieldTypeRedefs.captureNewInstanceForApplicableMockField(mock)) {
return true;
}
ParameterTypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterTypeRedefinitions();
if (paramTypeRedefinitions != null) {
CaptureOfNewInstances paramTypeCaptures = paramTypeRedefinitions.getCaptureOfNewInstances();
if (paramTypeCaptures != null && paramTypeCaptures.captureNewInstance(null, mock)) {
return true;
}
}
FieldTypeRedefinitions sharedFieldTypeRedefs = TestRun.getSharedFieldTypeRedefinitions();
assert sharedFieldTypeRedefs != null;
if (sharedFieldTypeRedefs.captureNewInstanceForApplicableMockField(mock)) {
return true;
}
}
return isCallToSuperClassConstructor(mock, classDesc);
}
private static boolean isCallToSuperClassConstructor(@NotNull Object mock, @NotNull String calledClassDesc)
{
Class> mockedClass = mock.getClass();
if (ClassNaming.isAnonymousClass(mockedClass)) {
// An anonymous class instantiation always invokes the constructor on the super-class,
// so that is the class we need to consider, not the anonymous one.
mockedClass = mockedClass.getSuperclass();
if (mockedClass == Object.class) {
return false;
}
}
String calledClassName = calledClassDesc.replace('/', '.');
return !calledClassName.equals(mockedClass.getName());
}
@NotNull private Phase getCurrentPhase()
{
ReplayPhase replay = replayPhase;
if (replay == null) {
assert recordPhase != null;
return recordPhase;
}
BaseVerificationPhase verification = verificationPhase;
if (verification == null) {
if (failureState.getErrorThrown() != null) {
// This can only happen when called from a catch/finally block.
throw failureState.getErrorThrown();
}
return replay;
}
return verification;
}
@NotNull public BaseVerificationPhase startVerifications(boolean inOrder)
{
if (replayPhase == null) {
throw new IllegalStateException("Not in the replay phase yet");
}
List expectations = replayPhase.nonStrictInvocations;
List