View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.internal.expectations;
6   
7   import static mockit.internal.util.Utilities.NO_ARGS;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.util.ArrayList;
13  import java.util.List;
14  import java.util.concurrent.locks.ReentrantLock;
15  
16  import mockit.Expectations;
17  import mockit.internal.expectations.invocation.ExpectedInvocation;
18  import mockit.internal.expectations.mocking.CaptureOfNewInstances;
19  import mockit.internal.expectations.mocking.FieldTypeRedefinitions;
20  import mockit.internal.expectations.mocking.PartialMocking;
21  import mockit.internal.expectations.mocking.TypeRedefinitions;
22  import mockit.internal.expectations.state.ExecutingTest;
23  import mockit.internal.state.TestRun;
24  import mockit.internal.util.ClassNaming;
25  import mockit.internal.util.DefaultValues;
26  import mockit.internal.util.ObjectMethods;
27  
28  public final class RecordAndReplayExecution {
29      public static final ReentrantLock RECORD_OR_REPLAY_LOCK = new ReentrantLock();
30      public static final ReentrantLock TEST_ONLY_PHASE_LOCK = new ReentrantLock();
31  
32      @Nullable
33      private final PartialMocking partialMocking;
34      @NonNull
35      private final PhasedExecutionState executionState;
36      @NonNull
37      private final FailureState failureState;
38      @Nullable
39      private RecordPhase recordPhase;
40      @Nullable
41      private ReplayPhase replayPhase;
42      @Nullable
43      private BaseVerificationPhase verificationPhase;
44  
45      public RecordAndReplayExecution() {
46          executionState = new PhasedExecutionState();
47          partialMocking = null;
48          discoverMockedTypesAndInstancesForMatchingOnInstance();
49          failureState = new FailureState();
50          replayPhase = new ReplayPhase(executionState, failureState);
51      }
52  
53      public RecordAndReplayExecution(@NonNull Expectations targetObject,
54              @Nullable Object... instancesToBePartiallyMocked) {
55          TestRun.enterNoMockingZone();
56          ExecutingTest executingTest = TestRun.getExecutingTest();
57          executingTest.setShouldIgnoreMockingCallbacks(true);
58  
59          try {
60              RecordAndReplayExecution previous = executingTest.getPreviousRecordAndReplay();
61  
62              executionState = previous == null ? new PhasedExecutionState() : previous.executionState;
63              failureState = new FailureState();
64              recordPhase = new RecordPhase(executionState);
65  
66              executingTest.setRecordAndReplay(this);
67              partialMocking = applyPartialMocking(instancesToBePartiallyMocked);
68              discoverMockedTypesAndInstancesForMatchingOnInstance();
69  
70              // noinspection LockAcquiredButNotSafelyReleased
71              TEST_ONLY_PHASE_LOCK.lock();
72          } catch (RuntimeException e) {
73              executingTest.setRecordAndReplay(null);
74              throw e;
75          } finally {
76              executingTest.setShouldIgnoreMockingCallbacks(false);
77              TestRun.exitNoMockingZone();
78          }
79      }
80  
81      private void discoverMockedTypesAndInstancesForMatchingOnInstance() {
82          TypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
83  
84          if (fieldTypeRedefinitions != null) {
85              List<Class<?>> fields = fieldTypeRedefinitions.getTargetClasses();
86              List<Class<?>> targetClasses = new ArrayList<>(fields);
87  
88              TypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterRedefinitions();
89  
90              if (paramTypeRedefinitions != null) {
91                  targetClasses.addAll(paramTypeRedefinitions.getTargetClasses());
92              }
93  
94              executionState.instanceBasedMatching.discoverMockedTypesToMatchOnInstances(targetClasses);
95  
96              if (partialMocking != null && !partialMocking.targetInstances.isEmpty()) {
97                  executionState.partiallyMockedInstances = new PartiallyMockedInstances(partialMocking.targetInstances);
98              }
99          }
100     }
101 
102     @Nullable
103     private static PartialMocking applyPartialMocking(@Nullable Object... instances) {
104         if (instances == null || instances.length == 0) {
105             return null;
106         }
107 
108         PartialMocking mocking = new PartialMocking();
109         mocking.redefineTypes(instances);
110         return mocking;
111     }
112 
113     @Nullable
114     public RecordPhase getRecordPhase() {
115         return recordPhase;
116     }
117 
118     /**
119      * Only to be called from generated bytecode or from the Mocking Bridge.
120      */
121     @Nullable
122     public static Object recordOrReplay(@Nullable Object mock, int mockAccess, @NonNull String classDesc,
123             @NonNull String mockDesc, @Nullable String genericSignature, int executionModeOrdinal,
124             @Nullable Object[] args) throws Throwable {
125         @NonNull
126         Object[] mockArgs = args == null ? NO_ARGS : args;
127         ExecutionMode executionMode = ExecutionMode.values()[executionModeOrdinal];
128 
129         if (notToBeMocked(mock, classDesc)) {
130             // This occurs if called from a custom argument matching method, in a call to an overridden Object method
131             // (equals, hashCode,
132             // toString), from a different thread during recording/verification, or during replay but between tests.
133             return defaultReturnValue(mock, classDesc, mockDesc, genericSignature, executionMode, mockArgs);
134         }
135 
136         ExecutingTest executingTest = TestRun.getExecutingTest();
137 
138         if (executingTest.isShouldIgnoreMockingCallbacks()) {
139             // This occurs when called from a reentrant delegate method, or during static initialization of a mocked
140             // class.
141             return defaultReturnValue(executingTest, mock, classDesc, mockDesc, genericSignature, executionMode,
142                     mockArgs);
143         }
144 
145         if (executingTest.shouldProceedIntoRealImplementation(mock, classDesc)
146                 || executionMode.isToExecuteRealImplementation(mock)) {
147             return Void.class;
148         }
149 
150         boolean isConstructor = mock != null && mockDesc.startsWith("<init>");
151         RECORD_OR_REPLAY_LOCK.lock();
152 
153         try {
154             RecordAndReplayExecution instance = executingTest.getOrCreateRecordAndReplay();
155 
156             if (isConstructor && instance.handleCallToConstructor(mock, classDesc)) {
157                 return instance.getResultForConstructor(mock, executionMode);
158             }
159 
160             return instance.getResult(mock, mockAccess, classDesc, mockDesc, genericSignature, executionMode, mockArgs);
161         } finally {
162             RECORD_OR_REPLAY_LOCK.unlock();
163         }
164     }
165 
166     private static boolean notToBeMocked(@Nullable Object mock, @NonNull String classDesc) {
167         return RECORD_OR_REPLAY_LOCK.isHeldByCurrentThread()
168                 || TEST_ONLY_PHASE_LOCK.isLocked() && !TEST_ONLY_PHASE_LOCK.isHeldByCurrentThread()
169                 || !TestRun.mockFixture().isStillMocked(mock, classDesc);
170     }
171 
172     @NonNull
173     private static Object defaultReturnValue(@Nullable Object mock, @NonNull String classDesc,
174             @NonNull String nameAndDesc, @Nullable String genericSignature, @NonNull ExecutionMode executionMode,
175             @NonNull Object[] args) {
176         if (executionMode.isToExecuteRealImplementation(mock)) {
177             return Void.class;
178         }
179 
180         if (mock != null) {
181             Object rv = ObjectMethods.evaluateOverride(mock, nameAndDesc, args);
182 
183             if (rv != null) {
184                 return executionMode.isToExecuteRealObjectOverride(mock) ? Void.class : rv;
185             }
186         }
187 
188         String returnTypeDesc = DefaultValues.getReturnTypeDesc(nameAndDesc);
189 
190         if (returnTypeDesc.charAt(0) == 'L') {
191             ExpectedInvocation invocation = new ExpectedInvocation(mock, classDesc, nameAndDesc, genericSignature,
192                     args);
193             Object cascadedInstance = invocation.getDefaultValueForReturnType();
194 
195             if (cascadedInstance != null) {
196                 return cascadedInstance;
197             }
198         }
199 
200         return Void.class;
201     }
202 
203     @Nullable
204     private static Object defaultReturnValue(@NonNull ExecutingTest executingTest, @Nullable Object mock,
205             @NonNull String classDesc, @NonNull String nameAndDesc, @Nullable String genericSignature,
206             @NonNull ExecutionMode executionMode, @NonNull Object[] args) throws Throwable {
207         RecordAndReplayExecution execution = executingTest.getCurrentRecordAndReplay();
208 
209         if (execution != null) {
210             Expectation recordedExpectation = execution.executionState.findExpectation(mock, classDesc, nameAndDesc,
211                     args);
212 
213             if (recordedExpectation != null) {
214                 return recordedExpectation.produceResult(mock, args);
215             }
216         }
217 
218         return defaultReturnValue(mock, classDesc, nameAndDesc, genericSignature, executionMode, args);
219     }
220 
221     private boolean handleCallToConstructor(@NonNull Object mock, @NonNull String classDesc) {
222         if (replayPhase != null) {
223             TypeRedefinitions paramTypeRedefinitions = TestRun.getExecutingTest().getParameterRedefinitions();
224 
225             if (paramTypeRedefinitions != null) {
226                 CaptureOfNewInstances paramTypeCaptures = paramTypeRedefinitions.getCaptureOfNewInstances();
227 
228                 if (paramTypeCaptures != null && paramTypeCaptures.captureNewInstance(null, mock)) {
229                     return true;
230                 }
231             }
232 
233             FieldTypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
234 
235             if (fieldTypeRedefinitions != null
236                     && fieldTypeRedefinitions.captureNewInstanceForApplicableMockField(mock)) {
237                 return true;
238             }
239         }
240 
241         return isCallToSuperClassConstructor(mock, classDesc);
242     }
243 
244     private static boolean isCallToSuperClassConstructor(@NonNull Object mock, @NonNull String calledClassDesc) {
245         Class<?> mockedClass = mock.getClass();
246 
247         if (ClassNaming.isAnonymousClass(mockedClass)) {
248             // An anonymous class instantiation always invokes the constructor on the super-class,
249             // so that is the class we need to consider, not the anonymous one.
250             mockedClass = mockedClass.getSuperclass();
251 
252             if (mockedClass == Object.class) {
253                 return false;
254             }
255         }
256 
257         String calledClassName = calledClassDesc.replace('/', '.');
258 
259         return !calledClassName.equals(mockedClass.getName());
260     }
261 
262     @Nullable
263     private Object getResultForConstructor(@NonNull Object mock, @NonNull ExecutionMode executionMode) {
264         return executionMode == ExecutionMode.Regular || executionMode == ExecutionMode.Partial && replayPhase == null
265                 || TestRun.getExecutingTest().isInjectableMock(mock) ? null : Void.class;
266     }
267 
268     @Nullable
269     private Object getResult(@Nullable Object mock, int mockAccess, @NonNull String classDesc, @NonNull String mockDesc,
270             @Nullable String genericSignature, @NonNull ExecutionMode executionMode, @NonNull Object[] args)
271             throws Throwable {
272         Phase currentPhase = getCurrentPhase();
273         failureState.clearErrorThrown();
274 
275         boolean withRealImpl = executionMode.isWithRealImplementation(mock);
276         Object result = currentPhase.handleInvocation(mock, mockAccess, classDesc, mockDesc, genericSignature,
277                 withRealImpl, args);
278 
279         failureState.reportErrorThrownIfAny();
280         return result;
281     }
282 
283     @NonNull
284     private Phase getCurrentPhase() {
285         ReplayPhase replay = replayPhase;
286 
287         if (replay == null) {
288             RecordPhase recordPhaseLocal = recordPhase;
289             assert recordPhaseLocal != null;
290             return recordPhaseLocal;
291         }
292 
293         BaseVerificationPhase verification = verificationPhase;
294 
295         if (verification != null) {
296             return verification;
297         }
298 
299         return replay;
300     }
301 
302     @NonNull
303     public BaseVerificationPhase startVerifications(boolean inOrder,
304             @Nullable Object[] mockedTypesAndInstancesToVerify) {
305         assert replayPhase != null;
306 
307         if (inOrder) {
308             verificationPhase = new OrderedVerificationPhase(replayPhase);
309         } else if (mockedTypesAndInstancesToVerify == null) {
310             verificationPhase = new UnorderedVerificationPhase(replayPhase);
311         } else {
312             verificationPhase = new FullVerificationPhase(replayPhase, mockedTypesAndInstancesToVerify);
313         }
314 
315         return verificationPhase;
316     }
317 
318     @Nullable
319     public static Error endCurrentReplayIfAny() {
320         RecordAndReplayExecution instance = TestRun.getRecordAndReplayForRunningTest();
321         return instance == null ? null : instance.endExecution();
322     }
323 
324     @Nullable
325     private Error endExecution() {
326         if (TEST_ONLY_PHASE_LOCK.isLocked()) {
327             TEST_ONLY_PHASE_LOCK.unlock();
328         }
329 
330         ReplayPhase replay = switchFromRecordToReplayIfNotYet();
331         Error error = replay.endExecution();
332 
333         if (error == null) {
334             error = failureState.getErrorThrownInAnotherThreadIfAny();
335         }
336 
337         if (error == null && verificationPhase != null) {
338             error = verificationPhase.endVerification();
339             verificationPhase = null;
340         }
341 
342         return error;
343     }
344 
345     @NonNull
346     private ReplayPhase switchFromRecordToReplayIfNotYet() {
347         if (replayPhase == null) {
348             recordPhase = null;
349             replayPhase = new ReplayPhase(executionState, failureState);
350         }
351 
352         return replayPhase;
353     }
354 
355     @Nullable
356     TestOnlyPhase getCurrentTestOnlyPhase() {
357         return recordPhase != null ? recordPhase : verificationPhase;
358     }
359 
360     void endInvocations() {
361         TEST_ONLY_PHASE_LOCK.unlock();
362 
363         if (verificationPhase == null) {
364             switchFromRecordToReplayIfNotYet();
365         } else {
366             Error error = verificationPhase.endVerification();
367             verificationPhase = null;
368 
369             if (error != null) {
370                 throw error;
371             }
372         }
373     }
374 }