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