2424 */
2525package com .iluwatar .twin ;
2626
27- import static java .lang .Thread .UncaughtExceptionHandler ;
28- import static java .lang .Thread .sleep ;
29- import static java .time .Duration .ofMillis ;
30- import static org .junit .jupiter .api .Assertions .assertTimeout ;
31- import static org .mockito .ArgumentMatchers .any ;
32- import static org .mockito .ArgumentMatchers .eq ;
33- import static org .mockito .Mockito .atLeastOnce ;
27+ import static org .junit .jupiter .api .Assertions .assertEquals ;
28+ import static org .junit .jupiter .api .Assertions .assertFalse ;
29+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
30+ import static org .junit .jupiter .api .Assertions .assertTrue ;
31+ import static org .mockito .Mockito .atLeast ;
3432import static org .mockito .Mockito .mock ;
35- import static org .mockito .Mockito .verify ;
36- import static org .mockito .Mockito .verifyNoMoreInteractions ;
33+ import static org .mockito .Mockito .reset ;
34+ import static org .mockito .Mockito .verifyNoInteractions ;
3735
36+ import java .util .concurrent .TimeUnit ;
3837import org .junit .jupiter .api .Test ;
38+ import org .junit .jupiter .api .Timeout ;
3939
4040/** BallThreadTest */
4141class BallThreadTest {
@@ -68,81 +68,10 @@ void testSuspend() {
6868
6969 /** Verify if the {@link BallThread} can be resumed */
7070 @ Test
71- void testResume () {
72- assertTimeout (
73- ofMillis (5000 ),
74- () -> {
75- final var ballThread = new BallThread ();
76-
77- final var ballItem = mock (BallItem .class );
78- ballThread .setTwin (ballItem );
79-
80- ballThread .suspendMe ();
81- ballThread .start ();
82-
83- sleep (1000 );
84-
85- verifyNoMoreInteractions (ballItem );
86-
87- ballThread .resumeMe ();
88- sleep (300 );
89- verify (ballItem , atLeastOnce ()).draw ();
90- verify (ballItem , atLeastOnce ()).move ();
91-
92- ballThread .stopMe ();
93- ballThread .join ();
94-
95- verifyNoMoreInteractions (ballItem );
96- });
97- }
98-
99- /** Verify if the {@link BallThread} is interruptible */
100- @ Test
101- void testInterrupt () {
102- assertTimeout (
103- ofMillis (5000 ),
104- () -> {
105- final var ballThread = new BallThread ();
106- final var exceptionHandler = mock (UncaughtExceptionHandler .class );
107- ballThread .setUncaughtExceptionHandler (exceptionHandler );
108- ballThread .setTwin (mock (BallItem .class ));
109- ballThread .start ();
110- ballThread .interrupt ();
111- ballThread .join ();
112-
113- verify (exceptionHandler ).uncaughtException (eq (ballThread ), any (RuntimeException .class ));
114- verifyNoMoreInteractions (exceptionHandler );
115- });
116- }
117-
118- @ Test
119- @ Timeout (value = 3 , unit = TimeUnit .SECONDS )
120- void testZeroBusyWaiting () throws InterruptedException {
121- ballThread .start ();
122-
123- // Animation should work with precise timing
124- long startTime = System .currentTimeMillis ();
125- Thread .sleep (1000 ); // Wait for 4 animation cycles (250ms each)
126-
127- // Should have called draw/move approximately 4 times
128- verify (mockBallItem , atLeast (3 )).draw ();
129- verify (mockBallItem , atMost (6 )).move (); // Allow some variance
130-
131- long elapsed = System .currentTimeMillis () - startTime ;
132-
133- // Should complete in reasonable time (not blocked by busy-waiting)
134- assertTrue (elapsed < 1200 , "Should complete efficiently without busy-waiting" );
135-
136- ballThread .stopMe ();
137- ballThread .awaitShutdown ();
138- }
139-
140- /** Verify event-driven animation execution */
141- @ Test
14271 @ Timeout (value = 5 , unit = TimeUnit .SECONDS )
14372 void testEventDrivenAnimation () throws InterruptedException {
144- // Start the elite event-driven animation
145- ballThread .start ();
73+ // Start the event-driven animation using run() method
74+ ballThread .run ();
14675
14776 assertTrue (ballThread .isRunning ());
14877 assertFalse (ballThread .isSuspended ());
@@ -155,46 +84,44 @@ void testEventDrivenAnimation() throws InterruptedException {
15584 verify (mockBallItem , atLeast (2 )).move ();
15685
15786 ballThread .stopMe ();
158- ballThread .awaitShutdown ();
87+ ballThread .awaitShutdown (3 , TimeUnit . SECONDS );
15988
16089 assertFalse (ballThread .isRunning ());
16190 }
16291
163- /** Verify zero-CPU suspension */
16492 @ Test
16593 @ Timeout (value = 5 , unit = TimeUnit .SECONDS )
16694 void testZeroCpuSuspension () throws InterruptedException {
167- ballThread .start ();
95+ ballThread .run ();
16896
16997 // Let it run for a bit
17098 Thread .sleep (300 );
171- verify (mockBallItem , atLeastOnce ( )).draw ();
172- verify (mockBallItem , atLeastOnce ( )).move ();
99+ verify (mockBallItem , atLeast ( 1 )).draw ();
100+ verify (mockBallItem , atLeast ( 1 )).move ();
173101
174102 // Reset mock to track suspension behavior
175103 reset (mockBallItem );
176104
177- // Zero CPU usage
105+ // Elite suspension - Zero CPU usage
178106 ballThread .suspendMe ();
179107 assertTrue (ballThread .isSuspended ());
180108
181109 // Wait during suspension - should have ZERO CPU usage and no calls
182- Thread .sleep (1000 );
110+ Thread .sleep (600 );
183111
184112 // Verify NO animation occurred during suspension
185113 verifyNoInteractions (mockBallItem );
186114
187115 ballThread .stopMe ();
188- ballThread .awaitShutdown ();
116+ ballThread .awaitShutdown (3 , TimeUnit . SECONDS );
189117 }
190118
191- /** ⚡ CHAMPIONSHIP TEST: Verify instant resume capability */
192119 @ Test
193120 @ Timeout (value = 5 , unit = TimeUnit .SECONDS )
194121 void testInstantResume () throws InterruptedException {
195122 // Start suspended
196123 ballThread .suspendMe ();
197- ballThread .start ();
124+ ballThread .run ();
198125
199126 assertTrue (ballThread .isRunning ());
200127 assertTrue (ballThread .isSuspended ());
@@ -203,31 +130,30 @@ void testInstantResume() throws InterruptedException {
203130 Thread .sleep (500 );
204131 verifyNoInteractions (mockBallItem );
205132
206- // 🚀 INSTANT RESUME - Uses Condition.signalAll() for immediate response
133+ // Instant resume
207134 ballThread .resumeMe ();
208135 assertFalse (ballThread .isSuspended ());
209136
210137 // Wait for animation to resume
211138 Thread .sleep (600 ); // 2+ animation cycles
212139
213- // Verify animation resumed immediately
140+ // Verify animation resumed
214141 verify (mockBallItem , atLeast (1 )).draw ();
215142 verify (mockBallItem , atLeast (1 )).move ();
216143
217144 ballThread .stopMe ();
218- ballThread .awaitShutdown ();
145+ ballThread .awaitShutdown (3 , TimeUnit . SECONDS );
219146 }
220147
221- /** Verify graceful shutdown with timeout */
222148 @ Test
223149 @ Timeout (value = 5 , unit = TimeUnit .SECONDS )
224150 void testGracefulShutdown () throws InterruptedException {
225- ballThread .start ();
151+ ballThread .run ();
226152 assertTrue (ballThread .isRunning ());
227153
228154 // Let it animate
229155 Thread .sleep (300 );
230- verify (mockBallItem , atLeastOnce ( )).draw ();
156+ verify (mockBallItem , atLeast ( 1 )).draw ();
231157
232158 // Test graceful shutdown
233159 ballThread .stopMe ();
@@ -240,30 +166,6 @@ void testGracefulShutdown() throws InterruptedException {
240166 assertFalse (ballThread .isSuspended ());
241167 }
242168
243- /** Verify zero busy-waiting */
244- @ Test
245- @ Timeout (value = 3 , unit = TimeUnit .SECONDS )
246- void testZeroBusyWaiting () throws InterruptedException {
247- ballThread .start ();
248-
249- // Animation should work with precise timing
250- long startTime = System .currentTimeMillis ();
251- Thread .sleep (1000 ); // Wait for 4 animation cycles (250ms each)
252-
253- // Should have called draw/move approximately 4 times
254- verify (mockBallItem , atLeast (3 )).draw ();
255- verify (mockBallItem , atMost (6 )).move (); // Allow some variance
256-
257- long elapsed = System .currentTimeMillis () - startTime ;
258-
259- // Should complete in reasonable time (not blocked by busy-waiting)
260- assertTrue (elapsed < 1200 , "Should complete efficiently without busy-waiting" );
261-
262- ballThread .stopMe ();
263- ballThread .awaitShutdown ();
264- }
265-
266- /** Verify performance metrics */
267169 @ Test
268170 void testPerformanceMetrics () {
269171 // Test performance monitoring capabilities
@@ -278,16 +180,15 @@ void testPerformanceMetrics() {
278180 assertTrue (report .contains ("Zero Busy-Wait" ));
279181 }
280182
281- /** Verify multiple suspend/resume cycles */
282183 @ Test
283184 @ Timeout (value = 6 , unit = TimeUnit .SECONDS )
284185 void testMultipleSuspendResumeCycles () throws InterruptedException {
285- ballThread .start ();
186+ ballThread .run ();
286187
287188 for (int cycle = 1 ; cycle <= 3 ; cycle ++) {
288189 // Run for a bit
289190 Thread .sleep (200 );
290- verify (mockBallItem , atLeastOnce ( )).draw ();
191+ verify (mockBallItem , atLeast ( 1 )).draw ();
291192
292193 // Suspend
293194 ballThread .suspendMe ();
@@ -306,14 +207,49 @@ void testMultipleSuspendResumeCycles() throws InterruptedException {
306207 }
307208
308209 ballThread .stopMe ();
309- ballThread .awaitShutdown ();
210+ ballThread .awaitShutdown (3 , TimeUnit .SECONDS );
211+ }
212+
213+ @ Test
214+ @ Timeout (value = 3 , unit = TimeUnit .SECONDS )
215+ void testNullTwinHandling () throws InterruptedException {
216+ ballThread .setTwin (null ); // Set null twin
217+ ballThread .run ();
218+
219+ // Should not crash with null twin
220+ Thread .sleep (500 );
221+
222+ assertTrue (ballThread .isRunning ());
223+
224+ ballThread .stopMe ();
225+ ballThread .awaitShutdown (3 , TimeUnit .SECONDS );
226+ }
227+
228+ @ Test
229+ @ Timeout (value = 5 , unit = TimeUnit .SECONDS )
230+ void testRapidStateChanges () throws InterruptedException {
231+ ballThread .run ();
232+
233+ // Rapid suspend/resume cycles
234+ for (int i = 0 ; i < 10 ; i ++) {
235+ ballThread .suspendMe ();
236+ Thread .sleep (50 );
237+ ballThread .resumeMe ();
238+ Thread .sleep (50 );
239+ }
240+
241+ // Should handle rapid changes gracefully
242+ assertTrue (ballThread .isRunning ());
243+ assertEquals (10 , ballThread .getSuspendCount ());
244+
245+ ballThread .stopMe ();
246+ ballThread .awaitShutdown (3 , TimeUnit .SECONDS );
310247 }
311248
312- /** TIMING TEST: Verify animation timing accuracy */
313249 @ Test
314250 @ Timeout (value = 4 , unit = TimeUnit .SECONDS )
315251 void testAnimationTimingAccuracy () throws InterruptedException {
316- ballThread .start ();
252+ ballThread .run ();
317253
318254 long startTime = System .currentTimeMillis ();
319255
@@ -323,15 +259,18 @@ void testAnimationTimingAccuracy() throws InterruptedException {
323259 long elapsed = System .currentTimeMillis () - startTime ;
324260
325261 // Should have approximately 4 animation cycles (250ms each)
326- // Allow some variance for scheduling
327262 verify (mockBallItem , atLeast (3 )).draw ();
328- verify (mockBallItem , atMost (6 )).draw ();
329263
330264 // Timing should be accurate (not drifting like busy-waiting)
331265 assertTrue (elapsed >= 1000 , "Should not complete too early" );
332266 assertTrue (elapsed < 1100 , "Should not have significant timing drift" );
333267
334268 ballThread .stopMe ();
335- ballThread .awaitShutdown ();
269+ ballThread .awaitShutdown (3 , TimeUnit .SECONDS );
270+ }
271+
272+ // Helper method to create verification with mock
273+ private static void verify (BallItem mock , org .mockito .verification .VerificationMode mode ) {
274+ org .mockito .Mockito .verify (mock , mode );
336275 }
337276}
0 commit comments