Skip to content

Commit df5d21a

Browse files
committed
adding unit tests for busy waiting
1 parent 4ba85f1 commit df5d21a

File tree

2 files changed

+348
-1
lines changed

2 files changed

+348
-1
lines changed

microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,118 @@ void whenDebugLogIsCollected_thenNoLogsShouldBeStored() {
6565
verifyNoInteractionsWithCentralLogStore();
6666
}
6767

68-
private static LogEntry createLogEntry(LogLevel logLevel, String message) {
68+
69+
@Test
70+
void whenTwoLogsCollected_thenBufferShouldContainThem() {
71+
// NEW TEST: Verify buffer state management
72+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Message 1"));
73+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Message 2"));
74+
75+
assertEquals(2, logAggregator.getLogCount());
76+
assertEquals(2, logAggregator.getBufferSize());
77+
78+
// Should not trigger flush yet (threshold is 3)
79+
verifyNoInteractionsWithCentralLogStore();
80+
}
81+
82+
@Test
83+
void whenScheduledFlushOccurs_thenBufferedLogsShouldBeStored() throws InterruptedException {
84+
// NEW TEST: Verify scheduled periodic flushing
85+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Scheduled flush test"));
86+
87+
assertEquals(1, logAggregator.getLogCount());
88+
verifyNoInteractionsWithCentralLogStore();
89+
90+
// Wait for scheduled flush (FLUSH_INTERVAL_SECONDS = 5)
91+
Thread.sleep(6000); // 5 seconds + buffer
92+
93+
verifyCentralLogStoreInvokedTimes(1);
94+
assertEquals(0, logAggregator.getLogCount());
95+
}
96+
97+
@Test
98+
void whenLogAggregatorStopped_thenRemainingLogsShouldBeStored() throws InterruptedException {
99+
// NEW TEST: Verify graceful shutdown flushes remaining logs
100+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Final message 1"));
101+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Final message 2"));
102+
103+
assertEquals(2, logAggregator.getLogCount());
104+
verifyNoInteractionsWithCentralLogStore();
105+
106+
// Stop should trigger final flush
107+
logAggregator.stop();
108+
logAggregator.awaitShutdown();
109+
110+
verifyCentralLogStoreInvokedTimes(2);
111+
assertEquals(0, logAggregator.getLogCount());
112+
assertFalse(logAggregator.isRunning());
113+
}
114+
115+
@Test
116+
void whenLogLevelBelowThreshold_thenLogShouldBeFiltered() {
117+
// 🎯 ENHANCED TEST: Test all log levels below INFO
118+
logAggregator.collectLog(createLogEntry(LogLevel.DEBUG, "Debug message"));
119+
logAggregator.collectLog(createLogEntry(LogLevel.TRACE, "Trace message"));
120+
121+
assertEquals(0, logAggregator.getLogCount());
122+
assertEquals(0, logAggregator.getBufferSize());
123+
verifyNoInteractionsWithCentralLogStore();
124+
}
125+
126+
@Test
127+
void whenLogLevelAtOrAboveThreshold_thenLogShouldBeAccepted() {
128+
// NEW TEST: Verify all accepted log levels
129+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Info message"));
130+
logAggregator.collectLog(createLogEntry(LogLevel.WARN, "Warning message"));
131+
logAggregator.collectLog(createLogEntry(LogLevel.ERROR, "Error message"));
132+
133+
assertEquals(3, logAggregator.getLogCount());
134+
assertEquals(3, logAggregator.getBufferSize());
135+
}
136+
137+
@Test
138+
void whenNullLogLevelProvided_thenLogShouldBeSkipped() {
139+
// EDGE CASE TEST: Null safety
140+
LogEntry nullLevelEntry = new LogEntry("ServiceA", null, "Null level message", LocalDateTime.now());
141+
142+
logAggregator.collectLog(nullLevelEntry);
143+
144+
assertEquals(0, logAggregator.getLogCount());
145+
verifyNoInteractionsWithCentralLogStore();
146+
}
147+
148+
@Test
149+
void whenLogAggregatorIsShutdown_thenNewLogsShouldBeRejected() throws InterruptedException {
150+
// NEW TEST: Verify shutdown behavior
151+
logAggregator.stop();
152+
logAggregator.awaitShutdown();
153+
154+
assertFalse(logAggregator.isRunning());
155+
156+
// Try to add log after shutdown
157+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Post-shutdown message"));
158+
159+
assertEquals(0, logAggregator.getLogCount());
160+
verifyNoInteractionsWithCentralLogStore();
161+
}
162+
163+
@Test
164+
void testPerformanceMetrics() throws InterruptedException {
165+
// CHAMPIONSHIP TEST: Verify performance monitoring
166+
assertTrue(logAggregator.isRunning());
167+
assertFalse(logAggregator.isSuspended());
168+
assertEquals(4.0, logAggregator.getFrameRate(), 0.1); // 1000ms / 250ms = 4 FPS
169+
170+
logAggregator.collectLog(createLogEntry(LogLevel.INFO, "Performance test"));
171+
assertEquals(1, logAggregator.getLogCount());
172+
173+
String report = logAggregator.getPerformanceReport();
174+
assertNotNull(report);
175+
assertTrue(report.contains("Event-Driven"));
176+
assertTrue(report.contains("Zero Busy-Wait"));
177+
}
178+
179+
private static LogEntry createLogEntry(LogLevel logLevel, String message) {
69180
return new LogEntry("ServiceA", logLevel, message, LocalDateTime.now());
70181
}
71182

twin/src/test/java/com/iluwatar/twin/BallThreadTest.java

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,240 @@ void testInterrupt() {
114114
verifyNoMoreInteractions(exceptionHandler);
115115
});
116116
}
117+
@Test
118+
@Timeout(value = 3, unit = TimeUnit.SECONDS)
119+
void testZeroBusyWaiting() throws InterruptedException {
120+
ballThread.start();
121+
122+
// Animation should work with precise timing
123+
long startTime = System.currentTimeMillis();
124+
Thread.sleep(1000); // Wait for 4 animation cycles (250ms each)
125+
126+
// Should have called draw/move approximately 4 times
127+
verify(mockBallItem, atLeast(3)).draw();
128+
verify(mockBallItem, atMost(6)).move(); // Allow some variance
129+
130+
long elapsed = System.currentTimeMillis() - startTime;
131+
132+
// Should complete in reasonable time (not blocked by busy-waiting)
133+
assertTrue(elapsed < 1200, "Should complete efficiently without busy-waiting");
134+
135+
ballThread.stopMe();
136+
ballThread.awaitShutdown();
137+
}
138+
139+
/**
140+
* Verify event-driven animation execution
141+
*/
142+
@Test
143+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
144+
void testEventDrivenAnimation() throws InterruptedException {
145+
// Start the elite event-driven animation
146+
ballThread.start();
147+
148+
assertTrue(ballThread.isRunning());
149+
assertFalse(ballThread.isSuspended());
150+
151+
// Wait for a few animation cycles (250ms intervals)
152+
Thread.sleep(800); // ~3 animation cycles
153+
154+
// Verify animation methods were called by scheduler
155+
verify(mockBallItem, atLeast(2)).draw();
156+
verify(mockBallItem, atLeast(2)).move();
157+
158+
ballThread.stopMe();
159+
ballThread.awaitShutdown();
160+
161+
assertFalse(ballThread.isRunning());
162+
}
163+
164+
/**
165+
* Verify zero-CPU suspension
166+
*/
167+
@Test
168+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
169+
void testZeroCpuSuspension() throws InterruptedException {
170+
ballThread.start();
171+
172+
// Let it run for a bit
173+
Thread.sleep(300);
174+
verify(mockBallItem, atLeastOnce()).draw();
175+
verify(mockBallItem, atLeastOnce()).move();
176+
177+
// Reset mock to track suspension behavior
178+
reset(mockBallItem);
179+
180+
// Zero CPU usage
181+
ballThread.suspendMe();
182+
assertTrue(ballThread.isSuspended());
183+
184+
// Wait during suspension - should have ZERO CPU usage and no calls
185+
Thread.sleep(1000);
186+
187+
// Verify NO animation occurred during suspension
188+
verifyNoInteractions(mockBallItem);
189+
190+
ballThread.stopMe();
191+
ballThread.awaitShutdown();
192+
}
193+
194+
/**
195+
* ⚡ CHAMPIONSHIP TEST: Verify instant resume capability
196+
*/
197+
@Test
198+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
199+
void testInstantResume() throws InterruptedException {
200+
// Start suspended
201+
ballThread.suspendMe();
202+
ballThread.start();
203+
204+
assertTrue(ballThread.isRunning());
205+
assertTrue(ballThread.isSuspended());
206+
207+
// Wait while suspended - no activity expected
208+
Thread.sleep(500);
209+
verifyNoInteractions(mockBallItem);
210+
211+
// 🚀 INSTANT RESUME - Uses Condition.signalAll() for immediate response
212+
ballThread.resumeMe();
213+
assertFalse(ballThread.isSuspended());
214+
215+
// Wait for animation to resume
216+
Thread.sleep(600); // 2+ animation cycles
217+
218+
// Verify animation resumed immediately
219+
verify(mockBallItem, atLeast(1)).draw();
220+
verify(mockBallItem, atLeast(1)).move();
221+
222+
ballThread.stopMe();
223+
ballThread.awaitShutdown();
224+
}
225+
226+
/**
227+
* Verify graceful shutdown with timeout
228+
*/
229+
@Test
230+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
231+
void testGracefulShutdown() throws InterruptedException {
232+
ballThread.start();
233+
assertTrue(ballThread.isRunning());
234+
235+
// Let it animate
236+
Thread.sleep(300);
237+
verify(mockBallItem, atLeastOnce()).draw();
238+
239+
// Test graceful shutdown
240+
ballThread.stopMe();
241+
242+
// Should complete shutdown within timeout
243+
boolean shutdownCompleted = ballThread.awaitShutdown(3, TimeUnit.SECONDS);
244+
assertTrue(shutdownCompleted, "Shutdown should complete within timeout");
245+
246+
assertFalse(ballThread.isRunning());
247+
assertFalse(ballThread.isSuspended());
248+
}
249+
250+
/**
251+
* Verify zero busy-waiting
252+
*/
253+
@Test
254+
@Timeout(value = 3, unit = TimeUnit.SECONDS)
255+
void testZeroBusyWaiting() throws InterruptedException {
256+
ballThread.start();
257+
258+
// Animation should work with precise timing
259+
long startTime = System.currentTimeMillis();
260+
Thread.sleep(1000); // Wait for 4 animation cycles (250ms each)
261+
262+
// Should have called draw/move approximately 4 times
263+
verify(mockBallItem, atLeast(3)).draw();
264+
verify(mockBallItem, atMost(6)).move(); // Allow some variance
265+
266+
long elapsed = System.currentTimeMillis() - startTime;
267+
268+
// Should complete in reasonable time (not blocked by busy-waiting)
269+
assertTrue(elapsed < 1200, "Should complete efficiently without busy-waiting");
270+
271+
ballThread.stopMe();
272+
ballThread.awaitShutdown();
273+
}
274+
275+
/**
276+
* Verify performance metrics
277+
*/
278+
@Test
279+
void testPerformanceMetrics() {
280+
// Test performance monitoring capabilities
281+
assertFalse(ballThread.isRunning());
282+
assertEquals(0, ballThread.getAnimationCycles());
283+
assertEquals(0, ballThread.getSuspendCount());
284+
assertEquals(4.0, ballThread.getFrameRate(), 0.1); // 1000ms / 250ms = 4 FPS
285+
286+
String report = ballThread.getPerformanceReport();
287+
assertNotNull(report);
288+
assertTrue(report.contains("Event-Driven"));
289+
assertTrue(report.contains("Zero Busy-Wait"));
290+
}
291+
292+
/**
293+
* Verify multiple suspend/resume cycles
294+
*/
295+
@Test
296+
@Timeout(value = 6, unit = TimeUnit.SECONDS)
297+
void testMultipleSuspendResumeCycles() throws InterruptedException {
298+
ballThread.start();
299+
300+
for (int cycle = 1; cycle <= 3; cycle++) {
301+
// Run for a bit
302+
Thread.sleep(200);
303+
verify(mockBallItem, atLeastOnce()).draw();
304+
305+
// Suspend
306+
ballThread.suspendMe();
307+
assertTrue(ballThread.isSuspended());
308+
309+
reset(mockBallItem); // Reset to track suspension
310+
Thread.sleep(200);
311+
verifyNoInteractions(mockBallItem); // No activity during suspension
312+
313+
// Resume
314+
ballThread.resumeMe();
315+
assertFalse(ballThread.isSuspended());
316+
317+
// Verify suspend count tracking
318+
assertEquals(cycle, ballThread.getSuspendCount());
319+
}
320+
321+
ballThread.stopMe();
322+
ballThread.awaitShutdown();
323+
}
324+
325+
/**
326+
* TIMING TEST: Verify animation timing accuracy
327+
*/
328+
@Test
329+
@Timeout(value = 4, unit = TimeUnit.SECONDS)
330+
void testAnimationTimingAccuracy() throws InterruptedException {
331+
ballThread.start();
332+
333+
long startTime = System.currentTimeMillis();
334+
335+
// Wait for exactly 1 second
336+
Thread.sleep(1000);
337+
338+
long elapsed = System.currentTimeMillis() - startTime;
339+
340+
// Should have approximately 4 animation cycles (250ms each)
341+
// Allow some variance for scheduling
342+
verify(mockBallItem, atLeast(3)).draw();
343+
verify(mockBallItem, atMost(6)).draw();
344+
345+
// Timing should be accurate (not drifting like busy-waiting)
346+
assertTrue(elapsed >= 1000, "Should not complete too early");
347+
assertTrue(elapsed < 1100, "Should not have significant timing drift");
348+
349+
ballThread.stopMe();
350+
ballThread.awaitShutdown();
351+
}
352+
117353
}

0 commit comments

Comments
 (0)