View Javadoc
1   package mockit;
2   
3   import static org.junit.jupiter.api.Assertions.assertEquals;
4   import static org.junit.jupiter.api.Assertions.assertFalse;
5   import static org.junit.jupiter.api.Assertions.assertNotNull;
6   import static org.junit.jupiter.api.Assertions.assertNull;
7   import static org.junit.jupiter.api.Assertions.assertThrows;
8   import static org.junit.jupiter.api.Assertions.assertTrue;
9   
10  import java.io.File;
11  import java.nio.file.Path;
12  
13  import org.junit.jupiter.api.Test;
14  import org.junit.jupiter.api.Timeout;
15  
16  /**
17   * The Class ReentrantFakeTest.
18   */
19  final class ReentrantFakeTest {
20  
21      /**
22       * The Class RealClass.
23       */
24      public static class RealClass {
25  
26          /**
27           * Foo.
28           *
29           * @return the string
30           */
31          public String foo() {
32              return "real value";
33          }
34  
35          /**
36           * Static recursive method.
37           *
38           * @param i
39           *            the i
40           *
41           * @return the int
42           */
43          protected static int staticRecursiveMethod(int i) {
44              return i <= 0 ? 0 : 2 + staticRecursiveMethod(i - 1);
45          }
46  
47          /**
48           * Recursive method.
49           *
50           * @param i
51           *            the i
52           *
53           * @return the int
54           */
55          public int recursiveMethod(int i) {
56              return i <= 0 ? 0 : 2 + recursiveMethod(i - 1);
57          }
58  
59          /**
60           * Non recursive static method.
61           *
62           * @param i
63           *            the i
64           *
65           * @return the int
66           */
67          protected static int nonRecursiveStaticMethod(int i) {
68              return -i;
69          }
70  
71          /**
72           * Non recursive method.
73           *
74           * @param i
75           *            the i
76           *
77           * @return the int
78           */
79          public int nonRecursiveMethod(int i) {
80              return -i;
81          }
82      }
83  
84      /**
85       * The Class AnnotatedFakeClass.
86       */
87      public static class AnnotatedFakeClass extends MockUp<RealClass> {
88  
89          /** The fake it. */
90          private static Boolean fakeIt;
91  
92          /**
93           * Foo.
94           *
95           * @param inv
96           *            the inv
97           *
98           * @return the string
99           */
100         @Mock
101         public String foo(Invocation inv) {
102             if (fakeIt == null) {
103                 throw new IllegalStateException("null fakeIt");
104             } else if (fakeIt) {
105                 return "fake value";
106             } else {
107                 return inv.proceed();
108             }
109         }
110     }
111 
112     /**
113      * Call fake method.
114      */
115     @Test
116     void callFakeMethod() {
117         new AnnotatedFakeClass();
118         AnnotatedFakeClass.fakeIt = true;
119 
120         String foo = new RealClass().foo();
121 
122         assertEquals("fake value", foo);
123     }
124 
125     /**
126      * Call original method.
127      */
128     @Test
129     void callOriginalMethod() {
130         new AnnotatedFakeClass();
131         AnnotatedFakeClass.fakeIt = false;
132 
133         String foo = new RealClass().foo();
134 
135         assertEquals("real value", foo);
136     }
137 
138     /**
139      * Called fake throws exception.
140      */
141     @Test
142     void calledFakeThrowsException() {
143         assertThrows(IllegalStateException.class, () -> {
144             new AnnotatedFakeClass();
145             AnnotatedFakeClass.fakeIt = null;
146 
147             new RealClass().foo();
148         });
149     }
150 
151     /**
152      * The Class FakeRuntime.
153      */
154     public static class FakeRuntime extends MockUp<Runtime> {
155 
156         /** The run finalization count. */
157         private int runFinalizationCount;
158 
159         /**
160          * Run finalization.
161          *
162          * @param inv
163          *            the inv
164          */
165         @Mock
166         public void runFinalization(Invocation inv) {
167             if (runFinalizationCount < 2) {
168                 inv.proceed();
169             }
170 
171             runFinalizationCount++;
172         }
173 
174         /**
175          * Removes the shutdown hook.
176          *
177          * @param inv
178          *            the inv
179          * @param hook
180          *            the hook
181          *
182          * @return true, if successful
183          */
184         @Mock
185         public boolean removeShutdownHook(Invocation inv, Thread hook) {
186             if (hook == null) {
187                 // noinspection AssignmentToMethodParameter
188                 hook = Thread.currentThread();
189             }
190 
191             return inv.proceed(hook);
192         }
193     }
194 
195     /**
196      * Call fake method for JRE class.
197      */
198     @Test
199     void callFakeMethodForJREClass() {
200         Runtime runtime = Runtime.getRuntime();
201         new FakeRuntime();
202 
203         runtime.runFinalization();
204         runtime.runFinalization();
205         runtime.runFinalization();
206 
207         assertFalse(runtime.removeShutdownHook(null));
208     }
209 
210     /**
211      * The Class ReentrantFakeForNativeMethod.
212      */
213     public static class ReentrantFakeForNativeMethod extends MockUp<Runtime> {
214 
215         /**
216          * Available processors.
217          *
218          * @param inv
219          *            the inv
220          *
221          * @return the int
222          */
223         @Mock
224         public int availableProcessors(Invocation inv) {
225             assertNotNull(inv.getInvokedInstance());
226             return 5;
227         }
228     }
229 
230     /**
231      * Apply reentrant fake for native JRE method.
232      */
233     @Test
234     void applyReentrantFakeForNativeJREMethod() {
235         new ReentrantFakeForNativeMethod();
236 
237         assertEquals(5, Runtime.getRuntime().availableProcessors());
238     }
239 
240     /**
241      * The Class MultiThreadedFake.
242      */
243     @SuppressWarnings("SynchronizeOnThis")
244     static class MultiThreadedFake extends MockUp<RealClass> {
245 
246         /** The nobody entered. */
247         private static boolean nobodyEntered = true;
248 
249         /**
250          * Foo.
251          *
252          * @param inv
253          *            the inv
254          *
255          * @return the string
256          *
257          * @throws InterruptedException
258          *             the interrupted exception
259          */
260         @Mock
261         public String foo(Invocation inv) throws InterruptedException {
262             String value = inv.proceed();
263 
264             synchronized (MultiThreadedFake.class) {
265                 if (nobodyEntered) {
266                     nobodyEntered = false;
267                     // noinspection WaitNotInLoop
268                     MultiThreadedFake.class.wait(5000);
269                 } else {
270                     MultiThreadedFake.class.notifyAll();
271                 }
272             }
273 
274             return value.replace("real", "fake");
275         }
276     }
277 
278     /**
279      * Two concurrent threads calling the same reentrant fake.
280      *
281      * @throws Exception
282      *             the exception
283      */
284     @Test
285     @Timeout(1000)
286     void twoConcurrentThreadsCallingTheSameReentrantFake() throws Exception {
287         new MultiThreadedFake();
288 
289         final StringBuilder first = new StringBuilder();
290         final StringBuilder second = new StringBuilder();
291 
292         Thread thread1 = new Thread(() -> first.append(new RealClass().foo()));
293         thread1.start();
294 
295         Thread thread2 = new Thread(() -> second.append(new RealClass().foo()));
296         thread2.start();
297 
298         thread1.join();
299         thread2.join();
300 
301         assertEquals("fake value", first.toString());
302         assertEquals("fake value", second.toString());
303     }
304 
305     /**
306      * The Class RealClass2.
307      */
308     public static final class RealClass2 {
309 
310         /**
311          * First method.
312          *
313          * @return the int
314          */
315         public int firstMethod() {
316             return 1;
317         }
318 
319         /**
320          * Second method.
321          *
322          * @return the int
323          */
324         public int secondMethod() {
325             return 2;
326         }
327     }
328 
329     /**
330      * Reentrant fake for non JRE class which calls another from A different thread.
331      */
332     @Test
333     void reentrantFakeForNonJREClassWhichCallsAnotherFromADifferentThread() {
334         new MockUp<RealClass2>() {
335             int value;
336 
337             @Mock
338             int firstMethod(Invocation inv) {
339                 return inv.proceed();
340             }
341 
342             @Mock
343             int secondMethod(Invocation inv) throws InterruptedException {
344                 final RealClass2 it = inv.getInvokedInstance();
345 
346                 Thread t = new Thread() {
347                     @Override
348                     public void run() {
349                         value = it.firstMethod();
350                     }
351                 };
352                 t.start();
353                 t.join();
354                 return value;
355             }
356         };
357 
358         RealClass2 r = new RealClass2();
359         assertEquals(1, r.firstMethod());
360         assertEquals(1, r.secondMethod());
361     }
362 
363     /**
364      * Reentrant fake for JRE class which calls another from A different thread.
365      */
366     @Test
367     void reentrantFakeForJREClassWhichCallsAnotherFromADifferentThread() {
368         System.setProperty("a", "1");
369         System.setProperty("b", "2");
370 
371         new MockUp<System>() {
372             String property;
373 
374             @Mock
375             String getProperty(Invocation inv, String key) {
376                 return inv.proceed();
377             }
378 
379             @Mock
380             String clearProperty(final String key) throws InterruptedException {
381                 Thread t = new Thread() {
382                     @Override
383                     public void run() {
384                         property = System.getProperty(key);
385                     }
386                 };
387                 t.start();
388                 t.join();
389                 return property;
390             }
391         };
392 
393         assertEquals("1", System.getProperty("a"));
394         assertEquals("2", System.clearProperty("b"));
395     }
396 
397     /**
398      * Fake file and force JRE to call reentrant faked method.
399      */
400     @Test
401     void fakeFileAndForceJREToCallReentrantFakedMethod() {
402         new MockUp<File>() {
403             @Mock
404             boolean exists(Invocation inv) {
405                 boolean exists = inv.proceed();
406                 return !exists;
407             }
408         };
409 
410         // Cause the JVM/JRE to load a new class, calling the faked File#exists() method in the process:
411         new Runnable() {
412             @Override
413             public void run() {
414             }
415         };
416 
417         assertTrue(Path.of("noFile").toFile().exists());
418     }
419 
420     /**
421      * The Class RealClass3.
422      */
423     public static final class RealClass3 {
424 
425         /**
426          * New instance.
427          *
428          * @return the real class 3
429          */
430         public RealClass3 newInstance() {
431             return new RealClass3();
432         }
433     }
434 
435     /**
436      * Reentrant fake for method which instantiates and returns new instance of the faked class.
437      */
438     @Test
439     void reentrantFakeForMethodWhichInstantiatesAndReturnsNewInstanceOfTheFakedClass() {
440         new MockUp<RealClass3>() {
441             @Mock
442             RealClass3 newInstance(Invocation inv) {
443                 return null;
444             }
445         };
446 
447         assertNull(new RealClass3().newInstance());
448     }
449 
450     /**
451      * The Class FakeClassWithReentrantFakeForRecursiveMethod.
452      */
453     public static final class FakeClassWithReentrantFakeForRecursiveMethod extends MockUp<RealClass> {
454 
455         /**
456          * Recursive method.
457          *
458          * @param inv
459          *            the inv
460          * @param i
461          *            the i
462          *
463          * @return the int
464          */
465         @Mock
466         int recursiveMethod(Invocation inv, int i) {
467             int j = inv.proceed();
468             return 1 + j;
469         }
470 
471         /**
472          * Static recursive method.
473          *
474          * @param inv
475          *            the inv
476          * @param i
477          *            the i
478          *
479          * @return the int
480          */
481         @Mock
482         static int staticRecursiveMethod(Invocation inv, int i) {
483             int j = inv.proceed();
484             return 1 + j;
485         }
486     }
487 
488     /**
489      * Reentrant fake method for recursive methods.
490      */
491     @Test
492     void reentrantFakeMethodForRecursiveMethods() {
493         assertEquals(0, RealClass.staticRecursiveMethod(0));
494         assertEquals(2, RealClass.staticRecursiveMethod(1));
495 
496         RealClass r = new RealClass();
497         assertEquals(0, r.recursiveMethod(0));
498         assertEquals(2, r.recursiveMethod(1));
499 
500         new FakeClassWithReentrantFakeForRecursiveMethod();
501 
502         assertEquals(1, RealClass.staticRecursiveMethod(0));
503         assertEquals(1 + 2 + 1, RealClass.staticRecursiveMethod(1));
504         assertEquals(1, r.recursiveMethod(0));
505         assertEquals(4, r.recursiveMethod(1));
506     }
507 
508     /**
509      * Fake that proceeds into recursive method.
510      */
511     @Test
512     void fakeThatProceedsIntoRecursiveMethod() {
513         RealClass r = new RealClass();
514         assertEquals(0, r.recursiveMethod(0));
515         assertEquals(2, r.recursiveMethod(1));
516 
517         new MockUp<RealClass>() {
518             @Mock
519             int recursiveMethod(Invocation inv, int i) {
520                 int ret = inv.proceed();
521                 return 1 + ret;
522             }
523         };
524 
525         assertEquals(1, r.recursiveMethod(0));
526         assertEquals(4, r.recursiveMethod(1));
527     }
528 
529     /**
530      * Recursive fake method without invocation parameter.
531      */
532     @Test
533     void recursiveFakeMethodWithoutInvocationParameter() {
534         new MockUp<RealClass>() {
535             @Mock
536             int nonRecursiveStaticMethod(int i) {
537                 if (i > 1) {
538                     return i;
539                 }
540                 return RealClass.nonRecursiveStaticMethod(i + 1);
541             }
542         };
543 
544         int result = RealClass.nonRecursiveStaticMethod(1);
545         assertEquals(2, result);
546     }
547 
548     /**
549      * Recursive fake method with invocation parameter not used for proceeding.
550      */
551     @Test
552     void recursiveFakeMethodWithInvocationParameterNotUsedForProceeding() {
553         new MockUp<RealClass>() {
554             @Mock
555             int nonRecursiveMethod(Invocation inv, int i) {
556                 if (i > 1) {
557                     return i;
558                 }
559                 RealClass it = inv.getInvokedInstance();
560                 return it.nonRecursiveMethod(i + 1);
561             }
562         };
563 
564         int result = new RealClass().nonRecursiveMethod(1);
565         assertEquals(2, result);
566     }
567 
568     /**
569      * Non recursive fake method with invocation parameter used for proceeding.
570      */
571     @Test
572     void nonRecursiveFakeMethodWithInvocationParameterUsedForProceeding() {
573         new MockUp<RealClass>() {
574             @Mock
575             int nonRecursiveMethod(Invocation inv, int i) {
576                 if (i > 1) {
577                     return i;
578                 }
579                 return inv.proceed(i + 1);
580             }
581         };
582 
583         int result = new RealClass().nonRecursiveMethod(1);
584         assertEquals(-2, result);
585     }
586 }