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