View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.integration;
7   
8   import static mockit.internal.reflection.ParameterReflection.getParameterCount;
9   
10  import edu.umd.cs.findbugs.annotations.NonNull;
11  import edu.umd.cs.findbugs.annotations.Nullable;
12  
13  import java.lang.reflect.Method;
14  import java.util.Collections;
15  import java.util.List;
16  
17  import mockit.internal.expectations.RecordAndReplayExecution;
18  import mockit.internal.expectations.invocation.MissingInvocation;
19  import mockit.internal.expectations.invocation.UnexpectedInvocation;
20  import mockit.internal.expectations.mocking.FieldTypeRedefinitions;
21  import mockit.internal.expectations.mocking.ParameterTypeRedefinitions;
22  import mockit.internal.expectations.mocking.TypeRedefinitions;
23  import mockit.internal.faking.FakeStates;
24  import mockit.internal.injection.InjectionProvider;
25  import mockit.internal.injection.TestedClassInstantiations;
26  import mockit.internal.injection.TestedParameters;
27  import mockit.internal.state.SavePoint;
28  import mockit.internal.state.TestRun;
29  import mockit.internal.util.ParameterNameExtractor;
30  import mockit.internal.util.StackTrace;
31  import mockit.internal.util.TestMethod;
32  
33  /**
34   * Base class for "test runner decorators", which provide integration between JMockit and specific test runners from
35   * JUnit and TestNG.
36   */
37  public class TestRunnerDecorator {
38      @Nullable
39      private static SavePoint savePointForTestClass;
40      @Nullable
41      private static SavePoint savePointForTest;
42  
43      /**
44       * A "volatile boolean" is as good as a java.util.concurrent.atomic.AtomicBoolean here, since we only need the basic
45       * get/set operations.
46       */
47      protected volatile boolean shouldPrepareForNextTest;
48  
49      protected TestRunnerDecorator() {
50          shouldPrepareForNextTest = true;
51      }
52  
53      protected static void updateTestClassState(@Nullable Object target, @NonNull Class<?> testClass) {
54          testClass = getActualTestClass(testClass);
55  
56          try {
57              handleSwitchToNewTestClassIfApplicable(testClass);
58  
59              if (target != null) {
60                  handleMockFieldsForWholeTestClass(target);
61              }
62          } catch (Error e) {
63              try {
64                  rollbackForTestClass();
65              } catch (Error err) {
66                  StackTrace.filterStackTrace(err);
67                  throw err;
68              }
69  
70              throw e;
71          } catch (RuntimeException e) {
72              rollbackForTestClass();
73              StackTrace.filterStackTrace(e);
74              throw e;
75          }
76      }
77  
78      @NonNull
79      private static Class<?> getActualTestClass(@NonNull Class<?> testClass) {
80          return testClass.isSynthetic() ? testClass.getSuperclass() : testClass;
81      }
82  
83      private static void handleSwitchToNewTestClassIfApplicable(@NonNull Class<?> testClass) {
84          Class<?> currentTestClass = TestRun.getCurrentTestClass();
85  
86          if (testClass != currentTestClass) {
87              if (currentTestClass == null) {
88                  savePointForTestClass = new SavePoint();
89              } else if (!currentTestClass.isAssignableFrom(testClass)) {
90                  cleanUpMocksFromPreviousTestClass();
91                  savePointForTestClass = new SavePoint();
92              }
93  
94              TestRun.setCurrentTestClass(testClass);
95          }
96      }
97  
98      public static void cleanUpMocksFromPreviousTestClass() {
99          cleanUpMocks(true);
100     }
101 
102     protected static void cleanUpMocksFromPreviousTest() {
103         cleanUpMocks(false);
104     }
105 
106     public static void cleanUpAllMocks() {
107         cleanUpMocks(true);
108         TestRun.getFakeClasses().discardStartupFakes();
109     }
110 
111     private static void cleanUpMocks(boolean forTestClassAsWell) {
112         discardTestLevelMockedTypes();
113 
114         if (forTestClassAsWell) {
115             rollbackForTestClass();
116         }
117 
118         clearFieldTypeRedefinitions();
119     }
120 
121     private static void rollbackForTestClass() {
122         SavePoint savePoint = savePointForTestClass;
123 
124         if (savePoint != null) {
125             savePoint.rollback();
126             savePointForTestClass = null;
127         }
128     }
129 
130     protected static void clearFieldTypeRedefinitions() {
131         TypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
132 
133         if (fieldTypeRedefinitions != null) {
134             fieldTypeRedefinitions.cleanUp();
135             TestRun.setFieldTypeRedefinitions(null);
136         }
137     }
138 
139     protected static void prepareForNextTest() {
140         if (savePointForTest == null) {
141             savePointForTest = new SavePoint();
142         }
143 
144         TestRun.prepareForNextTest();
145     }
146 
147     protected static void discardTestLevelMockedTypes() {
148         SavePoint savePoint = savePointForTest;
149 
150         if (savePoint != null) {
151             savePoint.rollback();
152             savePointForTest = null;
153         }
154     }
155 
156     protected static void handleMockFieldsForWholeTestClass(@NonNull Object target) {
157         Class<?> testClass = getActualTestClass(target.getClass());
158         FieldTypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
159 
160         if (fieldTypeRedefinitions == null) {
161             ParameterNameExtractor.extractNames(testClass);
162 
163             fieldTypeRedefinitions = new FieldTypeRedefinitions(testClass);
164             TestRun.setFieldTypeRedefinitions(fieldTypeRedefinitions);
165 
166             TestedClassInstantiations testedClassInstantiations = new TestedClassInstantiations();
167 
168             if (!testedClassInstantiations.findTestedAndInjectableMembers(testClass)) {
169                 testedClassInstantiations = null;
170             }
171 
172             TestRun.setTestedClassInstantiations(testedClassInstantiations);
173         }
174 
175         // noinspection ObjectEquality
176         if (target != TestRun.getCurrentTestInstance()) {
177             fieldTypeRedefinitions.assignNewInstancesToMockFields(target);
178         }
179     }
180 
181     protected static void createInstancesForTestedFieldsFromBaseClasses(@NonNull Object testClassInstance) {
182         TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
183 
184         if (testedClasses != null) {
185             TestRun.enterNoMockingZone();
186 
187             try {
188                 testedClasses.assignNewInstancesToTestedFieldsFromBaseClasses(testClassInstance);
189             } finally {
190                 TestRun.exitNoMockingZone();
191             }
192         }
193     }
194 
195     protected static void createInstancesForTestedFieldsBeforeSetup(@NonNull Object testClassInstance) {
196         TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
197 
198         if (testedClasses != null) {
199             TestRun.enterNoMockingZone();
200 
201             try {
202                 testedClasses.assignNewInstancesToTestedFields(testClassInstance, true,
203                         Collections.<InjectionProvider> emptyList());
204             } finally {
205                 TestRun.exitNoMockingZone();
206             }
207         }
208     }
209 
210     protected static void createInstancesForTestedFields(@NonNull Object testClassInstance) {
211         TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
212 
213         if (testedClasses != null) {
214             List<? extends InjectionProvider> injectableParameters = Collections.emptyList();
215             ParameterTypeRedefinitions paramTypeRedefs = TestRun.getExecutingTest().getParameterRedefinitions();
216 
217             if (paramTypeRedefs != null) {
218                 injectableParameters = paramTypeRedefs.getInjectableParameters();
219             }
220 
221             TestRun.enterNoMockingZone();
222 
223             try {
224                 testedClasses.assignNewInstancesToTestedFields(testClassInstance, false, injectableParameters);
225             } finally {
226                 TestRun.exitNoMockingZone();
227             }
228         }
229     }
230 
231     @Nullable
232     protected static Object[] createInstancesForAnnotatedParameters(@NonNull Object testClassInstance,
233             @NonNull Method testMethod, @Nullable Object[] parameterValues) {
234         int numParameters = getParameterCount(testMethod);
235 
236         if (numParameters == 0) {
237             return null;
238         }
239 
240         if (parameterValues == null || parameterValues.length != numParameters) {
241             // noinspection AssignmentToMethodParameter
242             parameterValues = new Object[numParameters];
243         }
244 
245         TestMethod methodInfo = new TestMethod(testMethod, parameterValues);
246 
247         TestRun.enterNoMockingZone();
248 
249         try {
250             ParameterTypeRedefinitions redefinitions = new ParameterTypeRedefinitions(methodInfo, parameterValues);
251             TestRun.getExecutingTest().setParameterRedefinitions(redefinitions);
252 
253             TestedParameters testedParameters = new TestedParameters(methodInfo);
254             List<? extends InjectionProvider> injectableParameters = redefinitions.getInjectableParameters();
255             testedParameters.createTestedParameters(testClassInstance, injectableParameters);
256         } finally {
257             TestRun.exitNoMockingZone();
258         }
259 
260         return parameterValues;
261     }
262 
263     protected static void concludeTestMethodExecution(@NonNull SavePoint savePoint, @Nullable Throwable thrownByTest,
264             boolean thrownAsExpected) throws Throwable {
265         TestRun.enterNoMockingZone();
266 
267         Error expectationsFailure = RecordAndReplayExecution.endCurrentReplayIfAny();
268         FakeStates fakeStates = TestRun.getFakeStates();
269 
270         try {
271             clearTestedObjectsIfAny();
272             if (expectationsFailure == null && (thrownByTest == null || thrownAsExpected)) {
273                 fakeStates.verifyMissingInvocations();
274             }
275         } finally {
276             fakeStates.resetExpectations();
277             savePoint.rollback();
278             TestRun.exitNoMockingZone();
279         }
280 
281         if (thrownByTest != null) {
282             if (expectationsFailure == null || !thrownAsExpected || isUnexpectedOrMissingInvocation(thrownByTest)) {
283                 throw thrownByTest;
284             }
285 
286             Throwable expectationsFailureCause = expectationsFailure.getCause();
287 
288             if (expectationsFailureCause != null) {
289                 expectationsFailureCause.initCause(thrownByTest);
290             }
291         }
292 
293         if (expectationsFailure != null) {
294             throw expectationsFailure;
295         }
296     }
297 
298     protected static void clearTestedObjectsIfAny() {
299         TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
300 
301         if (testedClasses != null) {
302             testedClasses.clearTestedObjects();
303         }
304     }
305 
306     protected static void clearTestedObjectsCreatedDuringSetup() {
307         TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
308 
309         if (testedClasses != null) {
310             testedClasses.clearTestedObjectsCreatedDuringSetup();
311         }
312     }
313 
314     private static boolean isUnexpectedOrMissingInvocation(@NonNull Throwable error) {
315         Class<?> errorType = error.getClass();
316         return errorType == UnexpectedInvocation.class || errorType == MissingInvocation.class;
317     }
318 }