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 automated developer testing.
It contains mocking/faking APIs and a code coverage tool, 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.
/*
* Copyright (c) 2006 JMockit developers
* 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 javax.annotation.*;
import static java.lang.reflect.Modifier.isAbstract;
import mockit.*;
import mockit.internal.expectations.invocation.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.expectations.state.*;
import mockit.internal.reflection.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
import static mockit.internal.util.GeneratedClasses.*;
import static mockit.internal.util.Utilities.*;
@SuppressWarnings("OverlyCoupledClass")
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 DynamicPartialMocking dynamicPartialMocking;
@Nonnull final PhasedExecutionState executionState;
@Nonnull private final FailureState failureState;
@Nullable private RecordPhase recordPhase;
@Nullable private ReplayPhase replayPhase;
@Nullable private BaseVerificationPhase verificationPhase;
public RecordAndReplayExecution() {
executionState = new PhasedExecutionState();
dynamicPartialMocking = null;
discoverMockedTypesAndInstancesForMatchingOnInstance();
failureState = new FailureState();
replayPhase = new ReplayPhase(this);
}
public RecordAndReplayExecution(@Nonnull Expectations targetObject, @Nullable Object... classesOrInstancesToBePartiallyMocked) {
TestRun.enterNoMockingZone();
ExecutingTest executingTest = TestRun.getExecutingTest();
executingTest.setShouldIgnoreMockingCallbacks(true);
try {
RecordAndReplayExecution previous = executingTest.getPreviousRecordAndReplay();
executionState = previous == null ? new PhasedExecutionState() : previous.executionState;
failureState = new FailureState();
recordPhase = new RecordPhase(this);
executingTest.setRecordAndReplay(this);
dynamicPartialMocking = applyDynamicPartialMocking(classesOrInstancesToBePartiallyMocked);
discoverMockedTypesAndInstancesForMatchingOnInstance();
//noinspection LockAcquiredButNotSafelyReleased
TEST_ONLY_PHASE_LOCK.lock();
}
catch (RuntimeException e) {
executingTest.setRecordAndReplay(null);
throw e;
}
finally {
executingTest.setShouldIgnoreMockingCallbacks(false);
TestRun.exitNoMockingZone();
}
}
private void discoverMockedTypesAndInstancesForMatchingOnInstance() {
TypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
if (fieldTypeRedefinitions != null) {
List> fields = fieldTypeRedefinitions.getTargetClasses();
List> targetClasses = new ArrayList<>(fields);
TypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterRedefinitions();
if (paramTypeRedefinitions != null) {
targetClasses.addAll(paramTypeRedefinitions.getTargetClasses());
}
executionState.discoverMockedTypesToMatchOnInstances(targetClasses);
if (dynamicPartialMocking != null && !dynamicPartialMocking.targetInstances.isEmpty()) {
executionState.setDynamicMockInstancesToMatch(dynamicPartialMocking.targetInstances);
}
}
}
@Nullable
private static DynamicPartialMocking applyDynamicPartialMocking(@Nullable Object... classesOrInstances) {
if (classesOrInstances == null || classesOrInstances.length == 0) {
return null;
}
DynamicPartialMocking mocking = new DynamicPartialMocking();
mocking.redefineTypes(classesOrInstances);
return mocking;
}
@Nullable
public RecordPhase getRecordPhase() { 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 @SuppressWarnings("OverlyComplexMethod")
public static Object recordOrReplay(
@Nullable Object mock, int mockAccess, @Nonnull String classDesc, @Nonnull String mockDesc, @Nullable String genericSignature,
int executionModeOrdinal, @Nullable Object[] args
) throws Throwable {
if (calledFromSpecialThread()) {
return proceedIntoRealImplementationOrGetDefaultReturnType(mock, mockAccess, mockDesc, genericSignature);
}
if (args == null) {
//noinspection AssignmentToMethodParameter
args = NO_ARGS;
}
ExecutionMode executionMode = ExecutionMode.values()[executionModeOrdinal];
if (
RECORD_OR_REPLAY_LOCK.isHeldByCurrentThread() ||
TEST_ONLY_PHASE_LOCK.isLocked() && !TEST_ONLY_PHASE_LOCK.isHeldByCurrentThread() ||
!TestRun.mockFixture().isStillMocked(mock, classDesc)
) {
// This occurs if called from a custom argument matching method, in a call to an overridden Object method (equals, hashCode,
// toString), from a different thread during recording/verification, or during replay but between tests.
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.
return defaultReturnValue(executingTest, mock, classDesc, mockDesc, genericSignature, executionMode, args);
}
if (executingTest.shouldProceedIntoRealImplementation(mock, classDesc) || executionMode.isToExecuteRealImplementation(mock)) {
return Void.class;
}
RECORD_OR_REPLAY_LOCK.lock();
try {
boolean isConstructor = mock != null && mockDesc.startsWith("");
RecordAndReplayExecution instance = executingTest.getOrCreateRecordAndReplay();
if (isConstructor && handleCallToConstructor(instance, mock, classDesc)) {
return
executionMode == ExecutionMode.Regular ||
executionMode == ExecutionMode.Partial && instance.replayPhase == null ||
executingTest.isInjectableMock(mock) ?
null : Void.class;
}
Phase currentPhase = instance.getCurrentPhase();
instance.failureState.clearErrorThrown();
boolean withRealImpl = executionMode.isWithRealImplementation(mock);
Object result = currentPhase.handleInvocation(mock, mockAccess, classDesc, mockDesc, genericSignature, withRealImpl, args);
instance.failureState.reportErrorThrownIfAny();
return result;
}
finally {
RECORD_OR_REPLAY_LOCK.unlock();
}
}
@Nullable
private static Object proceedIntoRealImplementationOrGetDefaultReturnType(
@Nullable Object mock, int mockAccess, @Nonnull String mockDesc, @Nullable String genericSignature
) {
if (mock != null) {
Class> mockedClass = mock.getClass();
String mockedClassName = mockedClass.getName();
if (isGeneratedImplementationClass(mockedClassName) || isAbstract(mockAccess) && isGeneratedSubclass(mockedClassName)) {
if (genericSignature != null) {
GenericTypeReflection typeReflection = new GenericTypeReflection(mockedClass, null);
String typeDesc = typeReflection.resolveReturnType(genericSignature);
return DefaultValues.computeForType(typeDesc);
}
return DefaultValues.computeForReturnType(mockDesc);
}
}
return Void.class;
}
@Nonnull
private static Object defaultReturnValue(
@Nullable Object mock, @Nonnull String classDesc, @Nonnull String nameAndDesc, @Nullable String genericSignature,
@Nonnull ExecutionMode executionMode, @Nonnull Object[] args
) {
if (executionMode.isToExecuteRealImplementation(mock)) {
return Void.class;
}
if (mock != null) {
Object rv = ObjectMethods.evaluateOverride(mock, nameAndDesc, args);
if (rv != null) {
return executionMode.isToExecuteRealObjectOverride(mock) ? Void.class : rv;
}
}
String returnTypeDesc = DefaultValues.getReturnTypeDesc(nameAndDesc);
if (returnTypeDesc.charAt(0) == 'L') {
ExpectedInvocation invocation = new ExpectedInvocation(mock, classDesc, nameAndDesc, genericSignature, args);
Object cascadedInstance = invocation.getDefaultValueForReturnType();
if (cascadedInstance != null) {
return cascadedInstance;
}
}
return Void.class;
}
@Nullable
private static Object defaultReturnValue(
@Nonnull ExecutingTest executingTest, @Nullable Object mock, @Nonnull String classDesc, @Nonnull String nameAndDesc,
@Nullable String genericSignature, @Nonnull ExecutionMode executionMode, @Nonnull Object[] args
) throws Throwable {
RecordAndReplayExecution execution = executingTest.getCurrentRecordAndReplay();
if (execution != null) {
Expectation recordedExpectation = execution.executionState.findExpectation(mock, classDesc, nameAndDesc, args);
if (recordedExpectation != null) {
return recordedExpectation.produceResult(mock, args);
}
}
return defaultReturnValue(mock, classDesc, nameAndDesc, genericSignature, executionMode, args);
}
private static boolean handleCallToConstructor(
@Nonnull RecordAndReplayExecution instance, @Nonnull Object mock, @Nonnull String classDesc
) {
if (instance.replayPhase != null) {
TypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterRedefinitions();
if (paramTypeRedefinitions != null) {
CaptureOfNewInstances paramTypeCaptures = paramTypeRedefinitions.getCaptureOfNewInstances();
if (paramTypeCaptures != null && paramTypeCaptures.captureNewInstance(mock)) {
return true;
}
}
FieldTypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
if (fieldTypeRedefinitions != null && fieldTypeRedefinitions.captureNewInstanceForApplicableMockField(mock)) {
return true;
}
}
return isCallToSuperClassConstructor(mock, classDesc);
}
private static boolean isCallToSuperClassConstructor(@Nonnull Object mock, @Nonnull 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());
}
@Nonnull
private Phase getCurrentPhase() {
ReplayPhase replay = replayPhase;
if (replay == null) {
RecordPhase record = recordPhase;
assert record != null;
return record;
}
BaseVerificationPhase verification = verificationPhase;
if (verification != null) {
return verification;
}
return replay;
}
@Nonnull
public BaseVerificationPhase startVerifications(boolean inOrder) {
assert replayPhase != null;
List expectations = replayPhase.invocations;
List