From f1c1cb266291def0bd3a502120e8f215877a8fdf Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Wed, 7 Aug 2024 11:48:27 +0200 Subject: [PATCH] Log exceptions to files in same directory of the test --- .../sdktesting/junit/ExecutionResult.kt | 110 +++++++++++++----- .../dev/restate/sdktesting/junit/utils.kt | 4 + .../kotlin/dev/restate/sdktesting/main.kt | 7 +- 3 files changed, 88 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/dev/restate/sdktesting/junit/ExecutionResult.kt b/src/main/kotlin/dev/restate/sdktesting/junit/ExecutionResult.kt index 916fd62..a227247 100644 --- a/src/main/kotlin/dev/restate/sdktesting/junit/ExecutionResult.kt +++ b/src/main/kotlin/dev/restate/sdktesting/junit/ExecutionResult.kt @@ -13,10 +13,15 @@ import com.github.ajalt.mordant.rendering.TextColors.red import com.github.ajalt.mordant.rendering.TextStyles.bold import com.github.ajalt.mordant.terminal.Terminal import java.io.PrintWriter -import java.lang.IllegalStateException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption import java.util.* +import kotlin.jvm.optionals.getOrNull import kotlin.math.min import kotlin.time.TimeSource +import org.junit.platform.engine.support.descriptor.ClassSource +import org.junit.platform.engine.support.descriptor.MethodSource import org.junit.platform.launcher.TestIdentifier import org.junit.platform.launcher.TestPlan @@ -37,8 +42,9 @@ class ExecutionResult( companion object { private const val TAB = " " - private const val DOUBLE_TAB = TAB + TAB - private const val DEFAULT_MAX_STACKTRACE_LINES = 15 + private const val DEFAULT_MAX_STACKTRACE_LINES_TERMINAL = 15 + private const val DEFAULT_MAX_STACKTRACE_LINES_FILE = 1000 + private const val TEST_EXCEPTIONS_FILE = "test-exceptions.log" private const val CAUSED_BY = "Caused by: " private const val SUPPRESSED = "Suppressed: " @@ -86,52 +92,94 @@ class ExecutionResult( .trimIndent()) } - fun printFailuresTo(terminal: Terminal, maxStackTraceLines: Int = DEFAULT_MAX_STACKTRACE_LINES) { - + fun printFailuresToTerminal( + terminal: Terminal, + maxStackTraceLines: Int = DEFAULT_MAX_STACKTRACE_LINES_TERMINAL + ) { val classesFailures = this.classesResults.toList().filter { it.second is Aborted || it.second is Failed } val testsFailures = this.testResults.toList().filter { it.second is Aborted || it.second is Failed } - fun printFailures(failureList: List>) { - for (failure in failureList) { - terminal.println("$TAB${describeTestIdentifier(testSuite, testPlan, failure.first)}") - describeTestIdentifierSource(terminal, failure.first) - when (failure.second) { - Aborted -> terminal.println("${DOUBLE_TAB}=> ABORTED") - is Failed -> { - val throwable = (failure.second as Failed).throwable - if (throwable == null) { - terminal.println("${DOUBLE_TAB}=> UNKNOWN FAILURE") - } else { - terminal.println("$DOUBLE_TAB=> $throwable") - printStackTrace(PrintWriter(System.out), throwable, maxStackTraceLines) - } - } - Succeeded -> throw IllegalStateException() - } - } - } - if (classesFailures.isEmpty() && testsFailures.isEmpty()) { return } terminal.println((red + bold)("== '$testSuite' FAILURES")) + val writer = PrintWriter(System.out) if (classesFailures.isNotEmpty()) { terminal.println("Classes initialization failures ${red(classesFailures.size.toString())}:") - printFailures(classesFailures) + for (failure in classesFailures) { + printFailure(writer, failure.first, failure.second, maxStackTraceLines) + } } - if (testsFailures.isNotEmpty()) { terminal.println("Test failures ${red(testsFailures.size.toString())}:") - printFailures(testsFailures) + for (failure in testsFailures) { + printFailure(writer, failure.first, failure.second, maxStackTraceLines) + } + } + } + + fun printFailuresToFiles( + baseReportDir: Path, + maxStackTraceLines: Int = DEFAULT_MAX_STACKTRACE_LINES_FILE + ) { + val reportDir = baseReportDir.resolve(testSuite) + + val classesFailures = + this.classesResults.toList().filter { it.second is Aborted || it.second is Failed } + val testsFailures = + this.testResults.toList().filter { it.second is Aborted || it.second is Failed } + + for (f in classesFailures) { + val clzSimpleName = classSimpleName((f.first.source.getOrNull() as ClassSource).className) + Files.newBufferedWriter( + reportDir.resolve(clzSimpleName).resolve(TEST_EXCEPTIONS_FILE), + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND) + .use { printFailure(PrintWriter(it), f.first, f.second, maxStackTraceLines) } + } + + for (f in testsFailures) { + // Resolve class name first + val clzSimpleName = classSimpleName((f.first.source.getOrNull() as MethodSource).className) + Files.newBufferedWriter( + reportDir.resolve(clzSimpleName).resolve(TEST_EXCEPTIONS_FILE), + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND) + .use { printFailure(PrintWriter(it), f.first, f.second, maxStackTraceLines) } + } + } + + private fun printFailure( + printWriter: PrintWriter, + testIdentifier: TestIdentifier, + result: TestResult, + maxStackTraceLines: Int + ) { + printWriter.println(describeTestIdentifier(testSuite, testPlan, testIdentifier)) + describeTestIdentifierSource(printWriter, testIdentifier) + when (result) { + Aborted -> printWriter.println("${TAB}=> ABORTED") + is Failed -> { + val throwable = result.throwable + if (throwable == null) { + printWriter.println("${TAB}=> UNKNOWN FAILURE") + } else { + printWriter.println("$TAB=> $throwable") + printStackTrace(printWriter, throwable, maxStackTraceLines) + } + } + Succeeded -> {} } } - private fun describeTestIdentifierSource(terminal: Terminal, testIdentifier: TestIdentifier) { - testIdentifier.source.ifPresent { terminal.println("${DOUBLE_TAB}$it") } + private fun describeTestIdentifierSource(writer: PrintWriter, testIdentifier: TestIdentifier) { + testIdentifier.source.ifPresent { writer.println("${TAB}$it") } } private fun printStackTrace(writer: PrintWriter, throwable: Throwable, max: Int) { @@ -140,7 +188,7 @@ class ExecutionResult( (throwable.suppressed != null && throwable.suppressed.size > 0)) { max = max / 2 } - printStackTrace(writer, arrayOf(), throwable, "", DOUBLE_TAB + " ", HashSet(), max) + printStackTrace(writer, arrayOf(), throwable, "", TAB + " ", HashSet(), max) writer.flush() } diff --git a/src/main/kotlin/dev/restate/sdktesting/junit/utils.kt b/src/main/kotlin/dev/restate/sdktesting/junit/utils.kt index 98d7f70..01ee638 100644 --- a/src/main/kotlin/dev/restate/sdktesting/junit/utils.kt +++ b/src/main/kotlin/dev/restate/sdktesting/junit/utils.kt @@ -24,3 +24,7 @@ fun describeTestIdentifier( describeTestIdentifier(testSuite, testPlan, testPlan.getParent(identifier).getOrNull()) return "$parent => ${identifier.displayName}" } + +fun classSimpleName(clz: String): String { + return clz.substringAfterLast('.') +} diff --git a/src/main/kotlin/dev/restate/sdktesting/main.kt b/src/main/kotlin/dev/restate/sdktesting/main.kt index 7e6a5eb..21529b5 100644 --- a/src/main/kotlin/dev/restate/sdktesting/main.kt +++ b/src/main/kotlin/dev/restate/sdktesting/main.kt @@ -159,6 +159,8 @@ Run test suite, executing the service as container. parallel) reports.add(report) + // No need to wait the end of the run for this + report.printFailuresToFiles(testRunnerOptions.reportDir) val failures = report.failedTests if (failures.isNotEmpty() || exclusions.isNotEmpty()) { newExclusions[testSuite.name] = @@ -208,7 +210,7 @@ Run test suite, executing the service as container. .trimIndent()) for (report in reports) { - report.printFailuresTo(terminal) + report.printFailuresToTerminal(terminal) } if (newFailures) { @@ -260,7 +262,8 @@ Run test suite, without executing the service inside a container. val report = testSuite.runTests(terminal, testRunnerOptions.reportDir, testFilters, true, false) - report.printFailuresTo(terminal) + report.printFailuresToTerminal(terminal) + report.printFailuresToFiles(testRunnerOptions.reportDir) if (report.failedTests.isNotEmpty()) { // Exit