/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.state;
import java.lang.reflect.*;
import java.util.*;
import javax.annotation.*;
import mockit.internal.*;
import mockit.internal.expectations.*;
import mockit.internal.expectations.mocking.*;
import static mockit.internal.util.Utilities.*;
@SuppressWarnings("ClassWithTooManyFields")
public final class ExecutingTest
{
@Nullable private RecordAndReplayExecution currentRecordAndReplay;
@Nullable private RecordAndReplayExecution recordAndReplayForLastTestMethod;
@Nonnull private final ThreadLocal<Boolean> shouldIgnoreMockingCallbacks;
@Nonnull private final ThreadLocal<BaseInvocation> proceedingInvocation;
private boolean proceeding;
@Nullable private ParameterTypeRedefinitions parameterTypeRedefinitions;
@Nonnull private final List<Object> regularMocks;
@Nonnull private final List<Object> injectableMocks;
@Nonnull private final List<Object> strictMocks;
@Nonnull private final Map<Object, Object> originalToCapturedInstance;
@Nonnull private final CascadingTypes cascadingTypes;
ExecutingTest()
{
shouldIgnoreMockingCallbacks = new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() { return false; }
};
proceedingInvocation = new ThreadLocal<BaseInvocation>();
regularMocks = new ArrayList<Object>();
injectableMocks = new ArrayList<Object>();
strictMocks = new ArrayList<Object>();
originalToCapturedInstance = new IdentityHashMap<Object, Object>(4);
cascadingTypes = new CascadingTypes();
}
@Nonnull
public RecordAndReplayExecution getOrCreateRecordAndReplay()
{
if (currentRecordAndReplay == null) {
setRecordAndReplay(new RecordAndReplayExecution());
}
return currentRecordAndReplay;
}
@Nullable
public RecordAndReplayExecution getPreviousRecordAndReplay()
{
RecordAndReplayExecution previous = currentRecordAndReplay;
currentRecordAndReplay = null;
return previous;
}
public void setRecordAndReplay(@Nullable RecordAndReplayExecution newRecordAndReplay)
{
recordAndReplayForLastTestMethod = null;
currentRecordAndReplay = newRecordAndReplay;
}
@Nullable public RecordAndReplayExecution getCurrentRecordAndReplay() { return currentRecordAndReplay; }
public boolean isShouldIgnoreMockingCallbacks() { return shouldIgnoreMockingCallbacks.get(); }
public boolean setShouldIgnoreMockingCallbacks(boolean flag)
{
boolean previousFlag = shouldIgnoreMockingCallbacks.get();
shouldIgnoreMockingCallbacks.set(flag);
return previousFlag;
}
public boolean isProceedingIntoRealImplementation()
{
boolean result = proceeding;
proceeding = false;
return result;
}
public void markAsProceedingIntoRealImplementation() { proceeding = true; }
@Nullable public BaseInvocation getProceedingInvocation() { return proceedingInvocation.get(); }
public void markAsProceedingIntoRealImplementation(@Nonnull BaseInvocation invocation)
{
BaseInvocation previousInvocation = proceedingInvocation.get();
if (previousInvocation != null) {
invocation.setPrevious(previousInvocation);
}
proceedingInvocation.set(invocation);
proceeding = true;
}
public boolean shouldProceedIntoRealImplementation(@Nullable Object mock, @Nonnull String classDesc)
{
BaseInvocation pendingInvocation = proceedingInvocation.get();
//noinspection SimplifiableIfStatement
if (pendingInvocation != null && pendingInvocation.isMethodInSuperclass(mock, classDesc)) {
return true;
}
return isProceedingIntoRealImplementation();
}
public void clearProceedingState()
{
BaseInvocation pendingInvocation = proceedingInvocation.get();
BaseInvocation previousInvocation = pendingInvocation.getPrevious();
proceedingInvocation.set(previousInvocation);
}
@Nonnull
RecordAndReplayExecution getRecordAndReplayForVerifications()
{
if (currentRecordAndReplay == null) {
if (recordAndReplayForLastTestMethod != null) {
currentRecordAndReplay = recordAndReplayForLastTestMethod;
}
else {
// This should only happen if no expectations at all were created by the whole test, but
// there is one (probably empty) verification block.
currentRecordAndReplay = new RecordAndReplayExecution();
}
}
//noinspection LockAcquiredButNotSafelyReleased
RecordAndReplayExecution.TEST_ONLY_PHASE_LOCK.lock();
return currentRecordAndReplay;
}
@Nullable public ParameterTypeRedefinitions getParameterRedefinitions() { return parameterTypeRedefinitions; }
public void setParameterRedefinitions(@Nonnull ParameterTypeRedefinitions redefinitions)
{
parameterTypeRedefinitions = redefinitions;
}
public void clearInjectableAndNonStrictMocks()
{
regularMocks.clear();
injectableMocks.clear();
originalToCapturedInstance.clear();
}
public void addInjectableMock(@Nonnull Object mock)
{
if (!isInjectableMock(mock)) {
injectableMocks.add(mock);
}
}
public boolean isInjectableMock(@Nonnull Object instance) { return containsReference(injectableMocks, instance); }
public boolean isMockedInstance(@Nonnull Object instance)
{
return containsReference(regularMocks, instance) || isInjectableMock(instance);
}
public void addCapturedInstanceForInjectableMock(@Nullable Object originalInstance, @Nonnull Object capturedInstance)
{
injectableMocks.add(capturedInstance);
addCapturedInstance(originalInstance, capturedInstance);
}
public void addCapturedInstance(@Nullable Object originalInstance, @Nonnull Object capturedInstance)
{
originalToCapturedInstance.put(capturedInstance, originalInstance);
}
public boolean isInvokedInstanceEquivalentToCapturedInstance(
@Nonnull Object invokedInstance, @Nonnull Object capturedInstance)
{
return
invokedInstance == originalToCapturedInstance.get(capturedInstance) ||
capturedInstance == originalToCapturedInstance.get(invokedInstance);
}
public void addStrictMock(@Nullable Object mock, @Nullable String mockClassDesc)
{
addStrictMock(mock);
if (mockClassDesc != null) {
String uniqueMockClassDesc = mockClassDesc.intern();
if (!containsStrictMock(uniqueMockClassDesc)) {
strictMocks.add(uniqueMockClassDesc);
}
}
}
private void addStrictMock(@Nullable Object mock)
{
if (mock != null && !containsStrictMock(mock)) {
strictMocks.add(mock);
}
}
private boolean containsStrictMock(@Nonnull Object mockOrClassDesc)
{
return containsReference(strictMocks, mockOrClassDesc);
}
public static boolean isInstanceMethodWithStandardBehavior(@Nullable Object mock, @Nonnull String nameAndDesc)
{
return
mock != null && nameAndDesc.charAt(0) != '<' &&
("equals(Ljava/lang/Object;)Z hashCode()I toString()Ljava/lang/String;".contains(nameAndDesc) ||
mock instanceof Comparable<?> && nameAndDesc.startsWith("compareTo(L") && nameAndDesc.endsWith(";)I"));
}
public void registerMock(@Nonnull MockedType mockedType, @Nonnull Object mock)
{
if (mockedType.injectable) {
addInjectableMock(mock);
}
else if (!containsReference(regularMocks, mock)) {
regularMocks.add(mock);
}
Type declaredType = mockedType.getDeclaredType();
cascadingTypes.addInstance(declaredType, mock);
}
public boolean isStrictInvocation(
@Nullable Object mock, @Nonnull String mockClassDesc, @Nonnull String mockNameAndDesc)
{
if (isInstanceMethodWithStandardBehavior(mock, mockNameAndDesc)) {
return false;
}
for (Object strictMock : strictMocks) {
if (strictMock == mock) {
return true;
}
else if (strictMock == mockClassDesc) {
addStrictMock(mock);
return true;
}
}
return false;
}
@Nonnull public CascadingTypes getCascadingTypes() { return cascadingTypes; }
void finishExecution()
{
recordAndReplayForLastTestMethod = currentRecordAndReplay;
currentRecordAndReplay = null;
if (parameterTypeRedefinitions != null) {
parameterTypeRedefinitions.cleanUp();
parameterTypeRedefinitions = null;
}
strictMocks.clear();
cascadingTypes.clearNonSharedCascadingTypes();
}
}