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