Skip to content

Commit d935fb9

Browse files
committed
Add more comprehensive information on timeout in BusyIndicator Test
1 parent c891c43 commit d935fb9

File tree

1 file changed

+174
-85
lines changed

1 file changed

+174
-85
lines changed

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java

Lines changed: 174 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
import static org.junit.jupiter.api.Assertions.assertNotEquals;
1818
import static org.junit.jupiter.api.Assertions.assertTrue;
1919

20+
import java.util.Map;
2021
import java.util.concurrent.CompletableFuture;
2122
import java.util.concurrent.CountDownLatch;
2223
import java.util.concurrent.ExecutorService;
2324
import java.util.concurrent.Executors;
2425
import java.util.concurrent.Future;
26+
import java.util.concurrent.ScheduledExecutorService;
27+
import java.util.concurrent.ScheduledFuture;
2528
import java.util.concurrent.TimeUnit;
29+
import java.util.concurrent.atomic.AtomicBoolean;
2630

2731
import org.eclipse.swt.SWT;
2832
import org.eclipse.swt.custom.BusyIndicator;
@@ -34,102 +38,187 @@
3438

3539
public class Test_org_eclipse_swt_custom_BusyIndicator {
3640

41+
private static final long LOOP_TIMEOUT_SECONDS = 5;
42+
private static final long WATCHDOG_TIMEOUT_SECONDS = 25;
43+
44+
private static class TestState {
45+
final Thread testThread = Thread.currentThread();
46+
final AtomicBoolean latchWaitEntered = new AtomicBoolean(false);
47+
final AtomicBoolean latchNestedWaitEntered = new AtomicBoolean(false);
48+
final AtomicBoolean showWhileStarted = new AtomicBoolean(false);
49+
final AtomicBoolean showWhileCompleted = new AtomicBoolean(false);
50+
final AtomicBoolean eventLoopDraining = new AtomicBoolean(false);
51+
volatile String currentStage = "initialization";
52+
53+
@Override
54+
public String toString() {
55+
return String.format(
56+
"TestState[stage=%s, testThread=%s, latchWaitEntered=%s, latchNestedWaitEntered=%s, showWhileStarted=%s, showWhileCompleted=%s, eventLoopDraining=%s]",
57+
currentStage, testThread.getName(), latchWaitEntered.get(), latchNestedWaitEntered.get(),
58+
showWhileStarted.get(), showWhileCompleted.get(), eventLoopDraining.get());
59+
}
60+
}
61+
62+
private static ScheduledFuture<?> startWatchdog(ScheduledExecutorService watchdogExecutor, TestState state) {
63+
return watchdogExecutor.schedule(() -> {
64+
System.err.println("=== WATCHDOG FIRED ===");
65+
System.err.println("Test state: " + state);
66+
System.err.println();
67+
System.err.println("=== THREAD DUMP ===");
68+
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
69+
for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {
70+
Thread thread = entry.getKey();
71+
StackTraceElement[] stackTrace = entry.getValue();
72+
System.err.println("Thread: " + thread.getName() + " (state=" + thread.getState() + ", id="
73+
+ thread.threadId() + ")");
74+
for (StackTraceElement element : stackTrace) {
75+
System.err.println(" at " + element);
76+
}
77+
System.err.println();
78+
}
79+
System.err.println("=== END THREAD DUMP ===");
80+
state.testThread.interrupt();
81+
}, WATCHDOG_TIMEOUT_SECONDS, TimeUnit.SECONDS);
82+
}
83+
84+
private static void drainEventQueue(Display display, TestState state) {
85+
state.eventLoopDraining.set(true);
86+
long startTime = System.nanoTime();
87+
long timeoutNanos = TimeUnit.SECONDS.toNanos(LOOP_TIMEOUT_SECONDS);
88+
while (!display.isDisposed() && display.readAndDispatch()) {
89+
if (System.nanoTime() - startTime > timeoutNanos) {
90+
throw new IllegalStateException(
91+
"Event queue not empty after " + LOOP_TIMEOUT_SECONDS + " seconds. State: " + state);
92+
}
93+
}
94+
state.eventLoopDraining.set(false);
95+
}
96+
3797
@Test
3898
@Timeout(value = 30)
3999
public void testShowWhile() {
40-
// Executors.newSingleThreadExecutor() hangs on some Linux configurations
41-
try (ExecutorService executor = Executors.newFixedThreadPool(2)){
42-
Shell shell = new Shell();
43-
Display display = shell.getDisplay();
44-
Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
45-
CountDownLatch latch = new CountDownLatch(1);
46-
CompletableFuture<?> future = CompletableFuture.runAsync(() -> {
47-
try {
48-
latch.await(10, TimeUnit.SECONDS);
49-
} catch (InterruptedException e) {
50-
}
51-
}, executor);
100+
TestState state = new TestState();
101+
try (ExecutorService executor = Executors.newFixedThreadPool(2);
102+
ScheduledExecutorService watchdogExecutor = Executors.newSingleThreadScheduledExecutor()) {
103+
ScheduledFuture<?> watchdog = startWatchdog(watchdogExecutor, state);
104+
try {
105+
state.currentStage = "creating shell and display";
106+
Shell shell = new Shell();
107+
Display display = shell.getDisplay();
108+
Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
109+
CountDownLatch latch = new CountDownLatch(1);
110+
state.currentStage = "creating main future";
111+
CompletableFuture<?> future = CompletableFuture.runAsync(() -> {
112+
state.latchWaitEntered.set(true);
113+
try {
114+
latch.await(10, TimeUnit.SECONDS);
115+
} catch (InterruptedException e) {
116+
}
117+
}, executor);
52118

53-
CountDownLatch latchNested = new CountDownLatch(1);
54-
CompletableFuture<?> futureNested = CompletableFuture.runAsync(() -> {
55-
try {
56-
latchNested.await(10, TimeUnit.SECONDS);
57-
} catch (InterruptedException e) {
58-
}
59-
}, executor);
60-
61-
assertNotEquals(busyCursor, shell.getCursor());
62-
63-
// This it proves that events on the display are executed
64-
display.asyncExec(() -> {
65-
// This will happen during the showWhile(future) from below.
66-
BusyIndicator.showWhile(futureNested);
67-
});
68-
69-
Cursor[] cursorInAsync = new Cursor[2];
70-
71-
// this serves two purpose:
72-
// 1) it proves that events on the display are executed
73-
// 2) it checks that the shell has the busy cursor during the nest showWhile.
74-
display.asyncExec(() -> {
75-
cursorInAsync[0] = shell.getCursor();
76-
latchNested.countDown();
77-
});
78-
79-
// this serves two purpose:
80-
// 1) it proves that events on the display are executed
81-
// 2) it checks that the shell has the busy cursor even after the termination of
82-
// the nested showWhile.
83-
display.asyncExec(() -> {
84-
cursorInAsync[1] = shell.getCursor();
85-
latch.countDown();
86-
});
87-
88-
BusyIndicator.showWhile(future);
89-
assertTrue(future.isDone());
90-
assertEquals(busyCursor, cursorInAsync[0]);
91-
assertEquals(busyCursor, cursorInAsync[1]);
92-
shell.dispose();
93-
while (!display.isDisposed() && display.readAndDispatch()) {
119+
CountDownLatch latchNested = new CountDownLatch(1);
120+
state.currentStage = "creating nested future";
121+
CompletableFuture<?> futureNested = CompletableFuture.runAsync(() -> {
122+
state.latchNestedWaitEntered.set(true);
123+
try {
124+
latchNested.await(10, TimeUnit.SECONDS);
125+
} catch (InterruptedException e) {
126+
}
127+
}, executor);
128+
129+
assertNotEquals(busyCursor, shell.getCursor());
130+
131+
// This it proves that events on the display are executed
132+
display.asyncExec(() -> {
133+
// This will happen during the showWhile(future) from below.
134+
BusyIndicator.showWhile(futureNested);
135+
});
136+
137+
Cursor[] cursorInAsync = new Cursor[2];
138+
139+
// this serves two purpose:
140+
// 1) it proves that events on the display are executed
141+
// 2) it checks that the shell has the busy cursor during the nest showWhile.
142+
display.asyncExec(() -> {
143+
cursorInAsync[0] = shell.getCursor();
144+
latchNested.countDown();
145+
});
146+
147+
// this serves two purpose:
148+
// 1) it proves that events on the display are executed
149+
// 2) it checks that the shell has the busy cursor even after the termination of
150+
// the nested showWhile.
151+
display.asyncExec(() -> {
152+
cursorInAsync[1] = shell.getCursor();
153+
latch.countDown();
154+
});
155+
156+
state.currentStage = "calling showWhile";
157+
state.showWhileStarted.set(true);
158+
BusyIndicator.showWhile(future);
159+
state.showWhileCompleted.set(true);
160+
assertTrue(future.isDone());
161+
assertEquals(busyCursor, cursorInAsync[0]);
162+
assertEquals(busyCursor, cursorInAsync[1]);
163+
shell.dispose();
164+
state.currentStage = "draining event queue";
165+
drainEventQueue(display, state);
166+
state.currentStage = "completed";
167+
} finally {
168+
watchdog.cancel(false);
94169
}
95170
}
96171
}
97172

98173
@Test
99174
@Timeout(value = 30)
100175
public void testShowWhileWithFuture() {
101-
try (ExecutorService executor = Executors.newSingleThreadExecutor()){
102-
Shell shell = new Shell();
103-
Display display = shell.getDisplay();
104-
Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
105-
Cursor[] cursorInAsync = new Cursor[1];
106-
CountDownLatch latch = new CountDownLatch(1);
107-
Future<?> future = executor.submit(() -> {
108-
try {
109-
latch.await(10, TimeUnit.SECONDS);
110-
} catch (InterruptedException e) {
111-
}
112-
});
113-
// this serves two purpose:
114-
// 1) it proves that events on the display are executed
115-
// 2) it checks that the shell has the busy cursor during the nest showWhile.
116-
display.asyncExec(() -> {
117-
cursorInAsync[0] = shell.getCursor();
118-
latch.countDown();
119-
});
120-
//External trigger for minimal latency as advised in the javadoc
121-
executor.submit(()->{
122-
try {
123-
future.get();
124-
} catch (Exception e) {
125-
}
126-
display.wake();
127-
});
128-
BusyIndicator.showWhile(future);
129-
assertTrue(future.isDone());
130-
assertEquals(busyCursor, cursorInAsync[0]);
131-
shell.dispose();
132-
while (!display.isDisposed() && display.readAndDispatch()) {
176+
TestState state = new TestState();
177+
try (ExecutorService executor = Executors.newSingleThreadExecutor();
178+
ScheduledExecutorService watchdogExecutor = Executors.newSingleThreadScheduledExecutor()) {
179+
ScheduledFuture<?> watchdog = startWatchdog(watchdogExecutor, state);
180+
try {
181+
state.currentStage = "creating shell and display";
182+
Shell shell = new Shell();
183+
Display display = shell.getDisplay();
184+
Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
185+
Cursor[] cursorInAsync = new Cursor[1];
186+
CountDownLatch latch = new CountDownLatch(1);
187+
state.currentStage = "creating future";
188+
Future<?> future = executor.submit(() -> {
189+
state.latchWaitEntered.set(true);
190+
try {
191+
latch.await(10, TimeUnit.SECONDS);
192+
} catch (InterruptedException e) {
193+
}
194+
});
195+
// this serves two purpose:
196+
// 1) it proves that events on the display are executed
197+
// 2) it checks that the shell has the busy cursor during the nest showWhile.
198+
display.asyncExec(() -> {
199+
cursorInAsync[0] = shell.getCursor();
200+
latch.countDown();
201+
});
202+
// External trigger for minimal latency as advised in the javadoc
203+
executor.submit(() -> {
204+
try {
205+
future.get();
206+
} catch (Exception e) {
207+
}
208+
display.wake();
209+
});
210+
state.currentStage = "calling showWhile";
211+
state.showWhileStarted.set(true);
212+
BusyIndicator.showWhile(future);
213+
state.showWhileCompleted.set(true);
214+
assertTrue(future.isDone());
215+
assertEquals(busyCursor, cursorInAsync[0]);
216+
shell.dispose();
217+
state.currentStage = "draining event queue";
218+
drainEventQueue(display, state);
219+
state.currentStage = "completed";
220+
} finally {
221+
watchdog.cancel(false);
133222
}
134223
}
135224
}

0 commit comments

Comments
 (0)