Skip to content

Commit 8bff465

Browse files
jaschdochfmehmed
authored andcommitted
Improve test exception handling
Adds a helper function that wraps `runTest` and tries to collect exceptions produced by other threads. Note that a race condition may still occur where the new helper method does not catch all exceptions. However, if it catches at least one exception, then one test fails and consequently CI fails (instead of being green with silent failures).
1 parent d67002a commit 8bff465

File tree

1 file changed

+54
-1
lines changed

1 file changed

+54
-1
lines changed

kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/AbstractKSPTest.kt

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ import org.junit.jupiter.api.AfterEach
5454
import org.junit.jupiter.api.Assertions
5555
import org.junit.jupiter.api.BeforeEach
5656
import org.junit.jupiter.api.TestInfo
57+
import java.awt.EventQueue
5758
import java.io.File
59+
import java.util.concurrent.ConcurrentLinkedQueue
5860

5961
abstract 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

Comments
 (0)