From 3379b29d28ccc68f68d61c4ea357fd0e75c041bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Tue, 20 May 2025 10:28:29 +0200 Subject: [PATCH] feat(runner-testballoon): Add TestBalloon support --- build.gradle.kts | 2 + docs/website/build.gradle.kts | 2 + gradle/libs.versions.toml | 3 + runners/runner-testballoon/README.md | 57 ++++++++++++++++ runners/runner-testballoon/build.gradle.kts | 41 +++++++++++ .../src/commonMain/kotlin/Suite.kt | 68 +++++++++++++++++++ .../src/commonTest/kotlin/TestBalloonTest.kt | 45 ++++++++++++ settings.gradle.kts | 1 + 8 files changed, 219 insertions(+) create mode 100644 runners/runner-testballoon/README.md create mode 100644 runners/runner-testballoon/build.gradle.kts create mode 100644 runners/runner-testballoon/src/commonMain/kotlin/Suite.kt create mode 100644 runners/runner-testballoon/src/commonTest/kotlin/TestBalloonTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2488555..9f200f0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,8 @@ dependencies { dokka(projects.suite) dokka(projects.runners.runnerKotest) dokka(projects.runners.runnerKotlinTest) + dokka(projects.runners.runnerTestInitiative) + dokka(projects.runners.runnerTestballoon) dokka(projects.compat.compatGradle) dokka(projects.compat.compatKotlinxDatetime) dokka(projects.compat.compatJavaTime) diff --git a/docs/website/build.gradle.kts b/docs/website/build.gradle.kts index 14d934c..eb6af93 100644 --- a/docs/website/build.gradle.kts +++ b/docs/website/build.gradle.kts @@ -8,6 +8,8 @@ dependencies { dokka(projects.suite) dokka(projects.runners.runnerKotest) dokka(projects.runners.runnerKotlinTest) + dokka(projects.runners.runnerTestInitiative) + dokka(projects.runners.runnerTestballoon) dokka(projects.compat.compatGradle) dokka(projects.compat.compatKotlinxDatetime) dokka(projects.compat.compatJavaTime) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77a8b3b..d921c97 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,8 +8,10 @@ gradle-testkit = "8.10.1" # https://gradle.org/releases/ parameterize = "0.3.3" # https://github.com/BenWoodworth/Parameterize/releases ktor = "3.0.3" # https://ktor.io/docs/releases.html#release-details kti = { strictly = "0.1.0" } # https://gitlab.com/opensavvy/groundwork/kotlin-test-initiative/-/releases +testBalloon = "0.3.0-K2.1.21" # https://github.com/infix-de/testBalloon/releases [plugins] +testBalloon = { id = "de.infix.testBalloon", version.ref = "testBalloon" } [libraries] kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } @@ -20,5 +22,6 @@ 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" } +testBalloon = { module = "de.infix.testBalloon:testBalloon-framework-core", version.ref = "testBalloon" } [bundles] diff --git a/runners/runner-testballoon/README.md b/runners/runner-testballoon/README.md new file mode 100644 index 0000000..f34afb8 --- /dev/null +++ b/runners/runner-testballoon/README.md @@ -0,0 +1,57 @@ +# Module Execute with TestBalloon + +Execute Prepared tests with the [TestBalloon framework](https://github.com/infix-de/testBalloon). + + + +## Configuration + +Add the TestBalloon Gradle plugin and a dependency on this library: +```kotlin +plugins { + kotlin("multiplatform") + id("de.infix.testBalloon") version "VERSION" //(1) +} + +kotlin { + // … + + sourceSets.commonTest.dependencies { + implementation("dev.opensavvy.prepared:runner-testballoon:VERSION") //(2) + } +} +``` + +1. [List of TestBalloon versions](https://github.com/infix-de/testBalloon/releases) +2. [List of Prepared versions](https://gitlab.com/opensavvy/groundwork/prepared/-/releases) + +## Usage + +To declare a Prepared suite using the TestBalloon runner, simply declare a top-level variable initialized using the [`preparedSuite`][opensavvy.prepared.runner.testballoon.preparedSuite] helper: +```kotlin +val MyTestSuite by preparedSuite { + test("Hello world!") { + assert(3 == 3) + } + + suite("My suite") { + test("First") { /* … */ } + test("Second") { /* … */ } + } +} +``` + +If you are already using TestBalloon and want to use Prepared features within an existing suite, use the [`withPrepared`][opensavvy.prepared.runner.testballoon.withPrepared] helper: +```kotlin +val MyMixedTestSuite by testSuite { + // Here, we're using TestBalloon syntax + test("Foo") { /* … */ } + testSuite("Bar") { /* … */ } + + withPrepared { + // Here, we're using Prepared syntax + test("Foo") { /* … */ } + suite("Bar") { /* … */ } + } +} +``` diff --git a/runners/runner-testballoon/build.gradle.kts b/runners/runner-testballoon/build.gradle.kts new file mode 100644 index 0000000..96b8fe4 --- /dev/null +++ b/runners/runner-testballoon/build.gradle.kts @@ -0,0 +1,41 @@ +@file:OptIn(ExperimentalWasmDsl::class) + +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl + +plugins { + alias(opensavvyConventions.plugins.base) + alias(opensavvyConventions.plugins.kotlin.library) + alias(libs.plugins.testBalloon) +} + +kotlin { + jvm() + js { + browser() + nodejs() + } + linuxX64() + wasmJs { + browser() + nodejs() + } + + sourceSets.commonMain { + dependencies { + api(projects.suite) + + api(libs.testBalloon) + } + } +} + +library { + name.set("Execute with TestBalloon") + description.set("Execute Prepared test suites with the TestBalloon multiplatform-first framework") + homeUrl.set("https://opensavvy.gitlab.io/groundwork/prepared/api-docs/runners/runner-testballoon/index.html") + + license.set { + name.set("Apache 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } +} diff --git a/runners/runner-testballoon/src/commonMain/kotlin/Suite.kt b/runners/runner-testballoon/src/commonMain/kotlin/Suite.kt new file mode 100644 index 0000000..1c15324 --- /dev/null +++ b/runners/runner-testballoon/src/commonMain/kotlin/Suite.kt @@ -0,0 +1,68 @@ +package opensavvy.prepared.runner.testballoon + +import de.infix.testBalloon.framework.* +import opensavvy.prepared.suite.PreparedDslMarker +import opensavvy.prepared.suite.SuiteDsl +import opensavvy.prepared.suite.TestDsl +import opensavvy.prepared.suite.config.* +import opensavvy.prepared.suite.config.TestConfig +import opensavvy.prepared.suite.runTestDslSuspend +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.time.Duration.Companion.seconds +import de.infix.testBalloon.framework.TestConfig as BalloonTestConfig + +@PreparedDslMarker +@TestDiscoverable +fun TestSuite.withPrepared( + config: TestConfig = TestConfig.Empty, + block: SuiteDsl.() -> Unit, +) { + TestBalloonSuite(this, config).apply(block) +} + +@PreparedDslMarker +@TestDiscoverable +fun preparedSuite( + @TestElementName name: String = "", + @TestDisplayName displayName: String = name, + balloonConfig: BalloonTestConfig = BalloonTestConfig, + preparedConfig: TestConfig = TestConfig.Empty, + content: SuiteDsl.() -> Unit, +) = testSuite(name, displayName, balloonConfig.chainedWith(preparedConfig.toBalloon())) { + withPrepared(preparedConfig) { + content() + } +} + +private class TestBalloonSuite( + private val upstream: TestSuite, + private val config: TestConfig +) : SuiteDsl { + override fun suite(name: String, config: TestConfig, block: SuiteDsl.() -> Unit) { + val effectiveConfig = this.config + config + upstream.testSuite(name, effectiveConfig.toBalloon()) { + withPrepared(effectiveConfig) { block() } + } + } + + override fun test(name: String, config: TestConfig, block: suspend TestDsl.() -> Unit) { + val effectiveConfig = this.config + config + upstream.test(name, effectiveConfig.toBalloon()) { + this.testScope.runTestDslSuspend(name, effectiveConfig, block) + } + } +} + +private fun TestConfig.toBalloon(): BalloonTestConfig { + var config = BalloonTestConfig + .testScope(isEnabled = true, timeout = this[CoroutineTimeout]?.duration ?: 60.seconds) + + if (this[Ignored] != null) + config = config.disable() + + if (this[Context].isNotEmpty()) + config = config.coroutineContext(this[Context].fold(EmptyCoroutineContext as CoroutineContext) { acc, it -> acc + it.context }) + + return config +} diff --git a/runners/runner-testballoon/src/commonTest/kotlin/TestBalloonTest.kt b/runners/runner-testballoon/src/commonTest/kotlin/TestBalloonTest.kt new file mode 100644 index 0000000..9559792 --- /dev/null +++ b/runners/runner-testballoon/src/commonTest/kotlin/TestBalloonTest.kt @@ -0,0 +1,45 @@ +package opensavvy.prepared.runner.testballoon + +import de.infix.testBalloon.framework.testSuite + +val TestBalloonSuite by testSuite { + test("string length") { + check(8 == "Test me!".length) + } + + testSuite("Foo") { + test("First") { + println("Success") + } + + test("Second") { + println("Success") + } + + withPrepared { + test("Third") { + println("Success") + } + + test("Fourth") { + println("Success") + } + } + } +} + +val PreparedBalloonedSuite by preparedSuite { + test("Foo") { + println("Success") + } + + suite("Bar") { + test("First") { + println("Success") + } + + test("Second") { + println("Success") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f8dcfc..241cb44 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,4 +70,5 @@ include( "runners:runner-kotlin-test", "runners:runner-kotest", "runners:runner-test-initiative", + "runners:runner-testballoon", ) -- 2.51.2