1
2
3
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
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
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
131
132
133 return defaultReturnValue(mock, classDesc, mockDesc, genericSignature, executionMode, mockArgs);
134 }
135
136 ExecutingTest executingTest = TestRun.getExecutingTest();
137
138 if (executingTest.isShouldIgnoreMockingCallbacks()) {
139
140
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
249
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 }