@@ -54,7 +54,9 @@ import org.junit.jupiter.api.AfterEach
5454import org.junit.jupiter.api.Assertions
5555import org.junit.jupiter.api.BeforeEach
5656import org.junit.jupiter.api.TestInfo
57+ import java.awt.EventQueue
5758import java.io.File
59+ import java.util.concurrent.ConcurrentLinkedQueue
5860
5961abstract class DisposableTest {
6062 private var _disposable : Disposable ? = null
@@ -241,7 +243,58 @@ abstract class AbstractKSPTest(frontend: FrontendKind<*>) : DisposableTest() {
241243 .takeWhile { ! it.startsWith(" // END" ) }
242244 .map { it.substring(3 ).trim() }
243245
244- val results = runTest(testServices, mainModule, libModules, testProcessor)
246+ val results = collectAllExceptions { runTest(testServices, mainModule, libModules, testProcessor) }
245247 Assertions .assertEquals(expectedResults.joinToString(" \n " ), results.joinToString(" \n " ))
246248 }
247249}
250+
251+ /* *
252+ * Collects exception from all threads when running `block`.
253+ * Throws a [[RuntimeException]] if any exception occurred.
254+ *
255+ * Note that function is not a perfect solution as it only catches exceptions
256+ * that happen during `block`.
257+ * Some threads may produce exceptions AFTER this function successfully returns,
258+ * but the purpose of this function is to help catch exceptions sometimes.
259+ */
260+ internal fun <A > collectAllExceptions (block : () -> A ): A {
261+ val exceptions = ConcurrentLinkedQueue <Throwable >()
262+
263+ // Save original default exception handler
264+ val originalDefaultThreadHandler = Thread .getDefaultUncaughtExceptionHandler()
265+
266+ // Override default handler and collect exceptions in `exceptions`.
267+ Thread .setDefaultUncaughtExceptionHandler { _, throwable -> exceptions.add(throwable) }
268+
269+ // Run the block
270+ val result = block()
271+
272+ // Flush the AWT event queue to process any pending events.
273+ // This helps catch exceptions from AWT/Swing components that might
274+ // occur asynchronously after the main block has completed.
275+ try {
276+ EventQueue .invokeAndWait { }
277+ } catch (e: Exception ) {
278+ // If flushing the queue itself causes an error, catch it.
279+ exceptions.add(e)
280+ }
281+
282+ // Restore original default exception handler
283+ Thread .setDefaultUncaughtExceptionHandler(originalDefaultThreadHandler)
284+
285+ if (exceptions.isNotEmpty()) {
286+ val message = buildString {
287+ append(" Failed with " )
288+ append(exceptions.size)
289+ appendLine(" uncaught errors:" )
290+ exceptions.forEach { exception ->
291+ appendLine(exception)
292+ exception.stackTrace.forEach { appendLine(it.toString()) }
293+ appendLine()
294+ appendLine()
295+ }
296+ }
297+ throw Exception (message)
298+ }
299+ return result
300+ }
0 commit comments