diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ba15dc..ff36858 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ kotlinx-datetime = "0.6.1" # https://github.com/Kotlin/kotlinx-datetime/relea gradle-testkit = "8.10.1" # https://gradle.org/releases/ parameterize = "0.3.3" # https://github.com/BenWoodworth/Parameterize/releases ktor = "3.0.1" # https://ktor.io/docs/releases.html#release-details +kti = { strictly = "0.1.0" } # https://gitlab.com/opensavvy/groundwork/kotlin-test-initiative/-/releases [plugins] @@ -18,5 +19,6 @@ gradle-testkit = { module = "dev.gradleplugins:gradle-test-kit", version.ref = " arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } parameterize = { module = "com.benwoodworth.parameterize:parameterize", version.ref = "parameterize" } ktor-testHost = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" } +kti = { module = "dev.opensavvy.kti:kotlin-test-initiative", version.ref = "kti" } [bundles] diff --git a/runners/runner-test-initiative/README.md b/runners/runner-test-initiative/README.md new file mode 100644 index 0000000..8e49c27 --- /dev/null +++ b/runners/runner-test-initiative/README.md @@ -0,0 +1,7 @@ +# Module Execute with Kotlin Test Initiative compatibility + +Execute Prepared tests with any runner compatible with the Kotlin Test Initiative. + + + +This module is highly experimental. To learn more, see the [Test Initiative repository](https://gitlab.com/opensavvy/groundwork/kotlin-test-initiative). diff --git a/runners/runner-test-initiative/build.gradle.kts b/runners/runner-test-initiative/build.gradle.kts new file mode 100644 index 0000000..0e251b2 --- /dev/null +++ b/runners/runner-test-initiative/build.gradle.kts @@ -0,0 +1,54 @@ +@file:OptIn(ExperimentalWasmDsl::class) + +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl + +plugins { + alias(opensavvyConventions.plugins.base) + alias(opensavvyConventions.plugins.kotlin.library) +} + +kotlin { + jvm() + js { + browser() + nodejs() + } + linuxX64() + linuxArm64() + macosX64() + macosArm64() + iosArm64() + iosX64() + iosSimulatorArm64() + watchosX64() + watchosArm32() + watchosArm64() + watchosSimulatorArm64() + tvosX64() + tvosArm64() + tvosSimulatorArm64() + mingwX64() + wasmJs { + browser() + nodejs() + } + + sourceSets.commonMain { + dependencies { + api(projects.suite) + + api(libs.kti) + } + } +} + +library { + name.set("Execute with Kotlin Test Initiative compatibility") + description.set("Execute Prepared test suites with Kotlin Test Initiative compatibility") + homeUrl.set("https://opensavvy.gitlab.io/groundwork/prepared/api-docs/runners/runner-test-initiative/index.html") + + license.set { + name.set("Apache 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 95020a3..818d965 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -68,4 +68,5 @@ include( "runners:runner-kotlin-test", "runners:runner-kotest", + "runners:runner-test-initiative", ) -- 2.51.2 From 94139984f2c7da82a785a9c85240be9db9895f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 11 Dec 2024 21:59:44 +0100 Subject: [PATCH 2/2] feat(runner-test-initiative): First prototype for the KTI runner --- .../src/commonMain/kotlin/Suite.kt | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 runners/runner-test-initiative/src/commonMain/kotlin/Suite.kt diff --git a/runners/runner-test-initiative/src/commonMain/kotlin/Suite.kt b/runners/runner-test-initiative/src/commonMain/kotlin/Suite.kt new file mode 100644 index 0000000..df8ccd8 --- /dev/null +++ b/runners/runner-test-initiative/src/commonMain/kotlin/Suite.kt @@ -0,0 +1,156 @@ +package opensavvy.prepared.runner.kti + +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.test.TestScope +import opensavvy.kti.* +import opensavvy.prepared.suite.SuiteDsl +import opensavvy.prepared.suite.TestDsl +import opensavvy.prepared.suite.config.Ignored +import opensavvy.prepared.suite.config.TestConfig +import opensavvy.prepared.suite.config.get +import opensavvy.prepared.suite.config.plus +import opensavvy.prepared.suite.runTestDslSuspend +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.coroutineContext + +@ExperimentalKtiApi +fun preparedSuite(@TestName name: String, block: SuiteDsl.() -> Unit): TestRoot = + PreparedTestRoot(name).apply { discover(block) } + +@ExperimentalKtiApi +private class PreparedTestRoot( + private val name: String, +) : TestRoot { + + private val builder = PreparedTestNode( + parentName = name, + parentConfig = TestConfig.Empty, + ) + + fun discover(block: SuiteDsl.() -> Unit) { + builder.block() + } + + override suspend fun execute(context: TestCommand, reporter: TestReporter) { + val suite = PreparedTestTree.Suite( + name = name, + config = TestConfig.Empty, + children = builder.children, + ) + + suite.execute(context, reporter) + } +} + +@ExperimentalKtiApi +private class PreparedTestNode( + private val parentName: String, + private val parentConfig: TestConfig, +) : SuiteDsl { + val children = ArrayList() + + override fun suite( + name: String, + config: TestConfig, + block: SuiteDsl.() -> Unit, + ) { + val fullName = "$parentName.$name" + val fullConfig = parentConfig + config + + children += PreparedTestTree.Suite( + name = fullName, + config = fullConfig, + children = PreparedTestNode(fullName, fullConfig) + .apply { block() } + .children + ) + } + + override fun test( + name: String, + context: CoroutineContext, + config: TestConfig, + block: suspend TestDsl.() -> Unit, + ) { + val fullName = "$parentName.$name" + val fullConfig = parentConfig + config + + children += PreparedTestTree.Test( + name = fullName, + context = context, + config = fullConfig, + ) { + block() + } + } +} + +@ExperimentalKtiApi +private sealed class PreparedTestTree { + + abstract val name: String + abstract val config: TestConfig + + abstract suspend fun execute( + context: TestCommand, + reporter: TestReporter, + ) + + class Suite( + override val name: String, + override val config: TestConfig, + val children: List, + ) : PreparedTestTree() { + + override suspend fun execute(context: TestCommand, reporter: TestReporter) { + for (child in children) { + child.execute(context, reporter) + } + } + } + + class Test( + override val name: String, + val context: CoroutineContext, + override val config: TestConfig, + val block: suspend TestDsl.() -> Unit, + ) : PreparedTestTree() { + + override suspend fun execute(context: TestCommand, reporter: TestReporter) { + val shouldRun = !context.dryRun && + (context.includes.toList().isEmpty() || context.includes.any { it matches name }) && + context.excludes.none { it matches name } && + config[Ignored] == null + + if (!shouldRun) { + reporter.report(object : TestEvent.Skipped { + override val testName: String + get() = name + }) + return + } + + reporter.report(object : TestEvent.Started { + override val testName: String + get() = name + }) + try { + val scope = TestScope(currentCoroutineContext()) + scope.runTestDslSuspend(name, this.context, config, block) + + reporter.report(object : TestEvent.Finished { + override val testName: String + get() = name + }) + } catch (e: Exception) { + coroutineContext.ensureActive() + println("Failed with: $e") // In the future, report that in the test event somehow + reporter.report(object : TestEvent.Failed { + override val testName: String + get() = name + }) + } + } + } +}