Skip to content

Commit

Permalink
Emojis!!!
Browse files Browse the repository at this point in the history
  • Loading branch information
slinkydeveloper committed Aug 2, 2024
1 parent e54747d commit f0007c3
Show file tree
Hide file tree
Showing 11 changed files with 405 additions and 75 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@ TODO, more coming soon
## Local debugging usage

* Run the service with your IDE and the debugger
* Run `java -jar restate-sdk-test-suite.jar debug --test-suite=<TEST_SUITE> --test-name=<TEST_NAME> default-service=9080`
* Run `java -jar restate-sdk-test-suite.jar debug --test-suite=<TEST_SUITE> --test-name=<TEST_NAME> default-service=9080`

## Releasing

Just push a new git tag:

```shell
git tag v1.1
git push --tags
```
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ repositories {
}

dependencies {
implementation("com.github.ajalt.clikt:clikt:4.2.2")
implementation(libs.clikt)
implementation(libs.mordant)

implementation(libs.restate.admin)
implementation(libs.restate.sdk.common)
Expand Down
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ ktor = "2.3.9"
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
mordant = { module = "com.github.ajalt.mordant:mordant", version = "2.7.2" }
clikt = { module = "com.github.ajalt.clikt:clikt", version = "4.2.2" }
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ data class ServiceSpec(
}
is LocalForwardServiceDeploymentConfig -> {
Testcontainers.exposeHostPorts(serviceConfig.port)
println(
LOG.warn(
"""
Service spec '$name' won't deploy a container, but will use locally running service deployment:
* Should be available at 'localhost:${serviceConfig.port}'
Expand Down
199 changes: 199 additions & 0 deletions src/main/kotlin/dev/restate/sdktesting/junit/ExecutionResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate SDK Test suite tool,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE
package dev.restate.sdktesting.junit

import com.github.ajalt.mordant.rendering.TextColors.green
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.util.*
import kotlin.math.min
import kotlin.time.TimeSource
import org.junit.platform.launcher.TestIdentifier
import org.junit.platform.launcher.TestPlan

class ExecutionResult(
val testSuite: String,
private val testPlan: TestPlan,
private val classesResults: Map<TestIdentifier, TestResult>,
private val testResults: Map<TestIdentifier, TestResult>,
timeStarted: TimeSource.Monotonic.ValueTimeMark,
timeFinished: TimeSource.Monotonic.ValueTimeMark
) {

val succeededTests = testResults.values.count { it is Succeeded }
val executedTests = testResults.size
val succeededClasses = classesResults.values.count { it is Succeeded }
val executedClasses = classesResults.size
val executionDuration = timeFinished - timeStarted

companion object {
private const val TAB = " "
private const val DOUBLE_TAB = TAB + TAB
private const val DEFAULT_MAX_STACKTRACE_LINES = 15

private const val CAUSED_BY = "Caused by: "
private const val SUPPRESSED = "Suppressed: "
private const val CIRCULAR = "Circular reference: "
}

sealed interface TestResult

data object Succeeded : TestResult

data object Aborted : TestResult

data class Failed(val throwable: Throwable?) : TestResult

val failedTests: List<TestIdentifier>
get() {
return classesResults
.toList()
.filter { it.second is Failed || it.second is Aborted }
.map { it.first } +
testResults
.toList()
.filter { it.second is Failed || it.second is Aborted }
.map { it.first }
}

fun printShortSummary(terminal: Terminal) {
// Compute test counters
val testsStyle = if (succeededTests == testResults.size) green else red
val testsInfoLine = testsStyle("""* Succeeded tests: $succeededTests / ${executedTests}""")

// Compute classes counters
val failedClasses = executedClasses - succeededClasses
val classesStyle = if (failedClasses != 0) red else green
val classesInfoLine = classesStyle("""* Failed classes initialization: $failedClasses""")

// Terminal print
terminal.println(
"""
${bold("==== $testSuite results")}
$testsInfoLine
$classesInfoLine
* Execution time: $executionDuration
"""
.trimIndent())
}

fun printFailuresTo(terminal: Terminal, maxStackTraceLines: Int = DEFAULT_MAX_STACKTRACE_LINES) {

val classesFailures =
this.classesResults.toList().filter { it.second is Aborted || it.second is Failed }
val testsFailures =
this.classesResults.toList().filter { it.second is Aborted || it.second is Failed }

fun printFailures(failureList: List<Pair<TestIdentifier, TestResult>>) {
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"))

if (classesFailures.isNotEmpty()) {
terminal.println("Classes initialization failures ${red(classesFailures.size.toString())}:")
printFailures(classesFailures)
}

if (testsFailures.isNotEmpty()) {
terminal.println("Test failures ${red(testsFailures.size.toString())}:")
printFailures(testsFailures)
}
}

private fun describeTestIdentifierSource(terminal: Terminal, testIdentifier: TestIdentifier) {
testIdentifier.source.ifPresent { terminal.println("${DOUBLE_TAB}$it") }
}

private fun printStackTrace(writer: PrintWriter, throwable: Throwable, max: Int) {
var max = max
if (throwable.cause != null ||
(throwable.suppressed != null && throwable.suppressed.size > 0)) {
max = max / 2
}
printStackTrace(writer, arrayOf(), throwable, "", DOUBLE_TAB + " ", HashSet(), max)
writer.flush()
}

private fun printStackTrace(
writer: PrintWriter,
parentTrace: Array<StackTraceElement>?,
throwable: Throwable?,
caption: String,
indentation: String,
seenThrowables: MutableSet<Throwable?>,
max: Int
) {
if (seenThrowables.contains(throwable)) {
writer.printf("%s%s[%s%s]%n", indentation, TAB, CIRCULAR, throwable)
return
}
seenThrowables.add(throwable)

val trace = throwable!!.stackTrace
if (parentTrace != null && parentTrace.size > 0) {
writer.printf("%s%s%s%n", indentation, caption, throwable)
}
val duplicates = numberOfCommonFrames(trace, parentTrace)
val numDistinctFrames = trace.size - duplicates
val numDisplayLines = min(numDistinctFrames.toDouble(), max.toDouble()).toInt()
for (i in 0 until numDisplayLines) {
writer.printf("%s%s%s%n", indentation, TAB, trace[i])
}
if (trace.size > max || duplicates != 0) {
writer.printf("%s%s%s%n", indentation, TAB, "[...]")
}

for (suppressed in throwable.suppressed) {
printStackTrace(writer, trace, suppressed, SUPPRESSED, indentation + TAB, seenThrowables, max)
}
if (throwable.cause != null) {
printStackTrace(writer, trace, throwable.cause, CAUSED_BY, indentation, seenThrowables, max)
}
}

private fun numberOfCommonFrames(
currentTrace: Array<StackTraceElement>,
parentTrace: Array<StackTraceElement>?
): Int {
var currentIndex = currentTrace.size - 1
var parentIndex = parentTrace!!.size - 1
while (currentIndex >= 0 && parentIndex >= 0) {
if (currentTrace[currentIndex] != parentTrace[parentIndex]) {
break
}
currentIndex--
parentIndex--
}
return currentTrace.size - 1 - currentIndex
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate SDK Test suite tool,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE
package dev.restate.sdktesting.junit

import dev.restate.sdktesting.junit.ExecutionResult.TestResult
import java.util.concurrent.ConcurrentHashMap
import kotlin.jvm.optionals.getOrNull
import kotlin.time.TimeSource
import org.junit.platform.engine.TestExecutionResult
import org.junit.platform.engine.support.descriptor.ClassSource
import org.junit.platform.engine.support.descriptor.MethodSource
import org.junit.platform.launcher.TestExecutionListener
import org.junit.platform.launcher.TestIdentifier
import org.junit.platform.launcher.TestPlan

class ExecutionResultCollector(private val testSuite: String) : TestExecutionListener {
private var testPlan: TestPlan? = null
private val classesResults: ConcurrentHashMap<TestIdentifier, TestResult> = ConcurrentHashMap()
private val testResults: ConcurrentHashMap<TestIdentifier, TestResult> = ConcurrentHashMap()

@Volatile private var timeStarted: TimeSource.Monotonic.ValueTimeMark? = null
@Volatile private var timeFinished: TimeSource.Monotonic.ValueTimeMark? = null

/** Get the summary generated by this listener. */
val results: ExecutionResult
get() {
return ExecutionResult(
testSuite,
testPlan!!,
classesResults.toMap(),
testResults.toMap(),
timeStarted!!,
timeFinished!!)
}

override fun testPlanExecutionStarted(testPlan: TestPlan) {
this.testPlan = testPlan
this.timeStarted = TimeSource.Monotonic.markNow()
}

override fun testPlanExecutionFinished(testPlan: TestPlan) {
this.timeFinished = TimeSource.Monotonic.markNow()
}

override fun executionFinished(
testIdentifier: TestIdentifier,
testExecutionResult: TestExecutionResult
) {
if (testIdentifier.source.getOrNull() is MethodSource && testIdentifier.isTest) {
testResults[testIdentifier] = testExecutionResult.toTestResult()
} else if (testIdentifier.source.getOrNull() is ClassSource) {
classesResults[testIdentifier] = testExecutionResult.toTestResult()
}
}

private fun TestExecutionResult.toTestResult(): TestResult {
return when (this.status!!) {
TestExecutionResult.Status.SUCCESSFUL -> ExecutionResult.Succeeded
TestExecutionResult.Status.ABORTED -> ExecutionResult.Aborted
TestExecutionResult.Status.FAILED -> ExecutionResult.Failed(this.throwable.getOrNull())
}
}
}
Loading

0 comments on commit f0007c3

Please sign in to comment.