All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.internal.expectations.RecordAndReplayExecution Maven / Gradle / Ivy

/*
 * Copyright (c) 2006 JMockit developers
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.internal.expectations;

import static mockit.internal.util.Utilities.NO_ARGS;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import mockit.Expectations;
import mockit.internal.expectations.invocation.ExpectedInvocation;
import mockit.internal.expectations.mocking.CaptureOfNewInstances;
import mockit.internal.expectations.mocking.FieldTypeRedefinitions;
import mockit.internal.expectations.mocking.PartialMocking;
import mockit.internal.expectations.mocking.TypeRedefinitions;
import mockit.internal.expectations.state.ExecutingTest;
import mockit.internal.state.TestRun;
import mockit.internal.util.ClassNaming;
import mockit.internal.util.DefaultValues;
import mockit.internal.util.ObjectMethods;

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 PartialMocking partialMocking;
    @NonNull
    private 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();
        partialMocking = null;
        discoverMockedTypesAndInstancesForMatchingOnInstance();
        failureState = new FailureState();
        replayPhase = new ReplayPhase(executionState, failureState);
    }

    public RecordAndReplayExecution(@NonNull Expectations targetObject,
            @Nullable Object... instancesToBePartiallyMocked) {
        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(executionState);

            executingTest.setRecordAndReplay(this);
            partialMocking = applyPartialMocking(instancesToBePartiallyMocked);
            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.instanceBasedMatching.discoverMockedTypesToMatchOnInstances(targetClasses);

            if (partialMocking != null && !partialMocking.targetInstances.isEmpty()) {
                executionState.partiallyMockedInstances = new PartiallyMockedInstances(partialMocking.targetInstances);
            }
        }
    }

    @Nullable
    private static PartialMocking applyPartialMocking(@Nullable Object... instances) {
        if (instances == null || instances.length == 0) {
            return null;
        }

        PartialMocking mocking = new PartialMocking();
        mocking.redefineTypes(instances);
        return mocking;
    }

    @Nullable
    public RecordPhase getRecordPhase() {
        return recordPhase;
    }

    /**
     * Only to be called from generated bytecode or from the Mocking Bridge.
     */
    @Nullable
    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 {
        @NonNull
        Object[] mockArgs = args == null ? NO_ARGS : args;
        ExecutionMode executionMode = ExecutionMode.values()[executionModeOrdinal];

        if (notToBeMocked(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, mockArgs);
        }

        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,
                    mockArgs);
        }

        if (executingTest.shouldProceedIntoRealImplementation(mock, classDesc)
                || executionMode.isToExecuteRealImplementation(mock)) {
            return Void.class;
        }

        boolean isConstructor = mock != null && mockDesc.startsWith("");
        RECORD_OR_REPLAY_LOCK.lock();

        try {
            RecordAndReplayExecution instance = executingTest.getOrCreateRecordAndReplay();

            if (isConstructor && instance.handleCallToConstructor(mock, classDesc)) {
                return instance.getResultForConstructor(mock, executionMode);
            }

            return instance.getResult(mock, mockAccess, classDesc, mockDesc, genericSignature, executionMode, mockArgs);
        } finally {
            RECORD_OR_REPLAY_LOCK.unlock();
        }
    }

    private static boolean notToBeMocked(@Nullable Object mock, @NonNull String classDesc) {
        return RECORD_OR_REPLAY_LOCK.isHeldByCurrentThread()
                || TEST_ONLY_PHASE_LOCK.isLocked() && !TEST_ONLY_PHASE_LOCK.isHeldByCurrentThread()
                || !TestRun.mockFixture().isStillMocked(mock, classDesc);
    }

    @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 boolean handleCallToConstructor(@NonNull Object mock, @NonNull String classDesc) {
        if (replayPhase != null) {
            TypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterRedefinitions();

            if (paramTypeRedefinitions != null) {
                CaptureOfNewInstances paramTypeCaptures = paramTypeRedefinitions.getCaptureOfNewInstances();

                if (paramTypeCaptures != null && paramTypeCaptures.captureNewInstance(null, 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());
    }

    @Nullable
    private Object getResultForConstructor(@NonNull Object mock, @NonNull ExecutionMode executionMode) {
        return executionMode == ExecutionMode.Regular || executionMode == ExecutionMode.Partial && replayPhase == null
                || TestRun.getExecutingTest().isInjectableMock(mock) ? null : Void.class;
    }

    @Nullable
    private Object getResult(@Nullable Object mock, int mockAccess, @NonNull String classDesc, @NonNull String mockDesc,
            @Nullable String genericSignature, @NonNull ExecutionMode executionMode, @NonNull Object[] args)
            throws Throwable {
        Phase currentPhase = getCurrentPhase();
        failureState.clearErrorThrown();

        boolean withRealImpl = executionMode.isWithRealImplementation(mock);
        Object result = currentPhase.handleInvocation(mock, mockAccess, classDesc, mockDesc, genericSignature,
                withRealImpl, args);

        failureState.reportErrorThrownIfAny();
        return result;
    }

    @NonNull
    private Phase getCurrentPhase() {
        ReplayPhase replay = replayPhase;

        if (replay == null) {
            RecordPhase recordPhaseLocal = recordPhase;
            assert recordPhaseLocal != null;
            return recordPhaseLocal;
        }

        BaseVerificationPhase verification = verificationPhase;

        if (verification != null) {
            return verification;
        }

        return replay;
    }

    @NonNull
    public BaseVerificationPhase startVerifications(boolean inOrder,
            @Nullable Object[] mockedTypesAndInstancesToVerify) {
        assert replayPhase != null;

        if (inOrder) {
            verificationPhase = new OrderedVerificationPhase(replayPhase);
        } else if (mockedTypesAndInstancesToVerify == null) {
            verificationPhase = new UnorderedVerificationPhase(replayPhase);
        } else {
            verificationPhase = new FullVerificationPhase(replayPhase, mockedTypesAndInstancesToVerify);
        }

        return verificationPhase;
    }

    @Nullable
    public static Error endCurrentReplayIfAny() {
        RecordAndReplayExecution instance = TestRun.getRecordAndReplayForRunningTest();
        return instance == null ? null : instance.endExecution();
    }

    @Nullable
    private Error endExecution() {
        if (TEST_ONLY_PHASE_LOCK.isLocked()) {
            TEST_ONLY_PHASE_LOCK.unlock();
        }

        ReplayPhase replay = switchFromRecordToReplayIfNotYet();
        Error error = replay.endExecution();

        if (error == null) {
            error = failureState.getErrorThrownInAnotherThreadIfAny();
        }

        if (error == null && verificationPhase != null) {
            error = verificationPhase.endVerification();
            verificationPhase = null;
        }

        return error;
    }

    @NonNull
    private ReplayPhase switchFromRecordToReplayIfNotYet() {
        if (replayPhase == null) {
            recordPhase = null;
            replayPhase = new ReplayPhase(executionState, failureState);
        }

        return replayPhase;
    }

    @Nullable
    TestOnlyPhase getCurrentTestOnlyPhase() {
        return recordPhase != null ? recordPhase : verificationPhase;
    }

    void endInvocations() {
        TEST_ONLY_PHASE_LOCK.unlock();

        if (verificationPhase == null) {
            switchFromRecordToReplayIfNotYet();
        } else {
            Error error = verificationPhase.endVerification();
            verificationPhase = null;

            if (error != null) {
                throw error;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy