From 63dba2510d94a45b274323605490634719e5a705 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze Date: Mon, 4 Jan 2021 01:02:53 +0100 Subject: [PATCH] feat: support kotest --- build.gradle.kts | 27 ++++++++----- kotest/build.gradle.kts | 29 ++++++++++++++ .../src/main/kotlin/KotestTestFilesAdapter.kt | 40 +++++++++++++++++++ kotest/src/main/kotlin/testFiles.kt | 13 ++++++ .../kotlin/SpekTestFilesIntegrationSpec.kt | 39 ++++++++++++++++++ kotest/src/test/kotlin/samples/ExampleSpek.kt | 22 ++++++++++ spek/build.gradle.kts | 4 +- spek/src/main/kotlin/testFiles.kt | 2 +- .../test/kotlin/{ => samples}/ExampleSpek.kt | 3 +- src/main/kotlin/DefaultTestFiles.kt | 12 +++--- 10 files changed, 173 insertions(+), 18 deletions(-) create mode 100644 kotest/src/main/kotlin/KotestTestFilesAdapter.kt create mode 100644 kotest/src/main/kotlin/testFiles.kt create mode 100644 kotest/src/test/kotlin/SpekTestFilesIntegrationSpec.kt create mode 100644 kotest/src/test/kotlin/samples/ExampleSpek.kt rename spek/src/test/kotlin/{ => samples}/ExampleSpek.kt (87%) diff --git a/build.gradle.kts b/build.gradle.kts index cc2fb90..d6e029c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -86,6 +86,13 @@ evaluationDependsOnChildren() val closeAndReleaseRepository by project.tasks allprojects { + apply { + plugin("org.jetbrains.dokka") + plugin("de.marcphilipp.nexus-publish") + plugin("org.gradle.maven-publish") + plugin("org.gradle.signing") + } + val sourcesJar by tasks.registering(Jar::class) { group = "build" description = "Assembles the source code into a jar" @@ -116,15 +123,23 @@ allprojects { archives(dokkaJar) } - rootProject.publishing.publications.register(name) { + signing { + val signingKey: String? by project + val signingKeyPassword: String? by project + useInMemoryPgpKeys(signingKey, signingKeyPassword) + } + + publishing.publications.register("maven") { + artifactId = if (extra.has("artifactId")) extra["artifactId"] as String else project.name + from(components["java"]) artifact(sourcesJar) artifact(dokkaJar) - rootProject.signing.sign(this) + signing.sign(this) pom { - name.set(provider { "$groupId:$artifactId" }) + name.set("$groupId:$artifactId") description.set("Easily manage test files and directories when testing with Spek!") inceptionYear.set("2020") url.set("https://github.com/$githubRepository") @@ -171,12 +186,6 @@ val mavenCentral = nexusPublishing.repositories.sonatype { password.set(ossrhPassword) } -signing { - val signingKey: String? by project - val signingKeyPassword: String? by project - useInMemoryPgpKeys(signingKey, signingKeyPassword) -} - closeAndReleaseRepository.mustRunAfter(mavenCentral.publishTask) tasks.register("release") { diff --git a/kotest/build.gradle.kts b/kotest/build.gradle.kts index 059e50d..29f514e 100644 --- a/kotest/build.gradle.kts +++ b/kotest/build.gradle.kts @@ -1,4 +1,5 @@ import org.gradle.api.JavaVersion.VERSION_1_8 +import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -6,7 +7,17 @@ plugins { id("org.jetbrains.dokka") } +val artifactId by extra("kotest-files") + dependencies { + val kotestVersion = "4.3.2" + + implementation(rootProject) + // Kotest is a peer dependency + compileOnly(name = "kotest-framework-api", version = kotestVersion, group = "io.kotest") + + testImplementation(name = "kotest-runner-junit5", version = kotestVersion, group = "io.kotest") + testImplementation(name = "atrium-fluent-en_GB", version = "0.15.0", group = "ch.tutteli.atrium") } java { @@ -23,3 +34,21 @@ tasks.withType { jvmTarget = "1.8" } } + +tasks.compileTestKotlin { + kotlinOptions { + freeCompilerArgs += "-Xopt-in=kotlin.io.path.ExperimentalPathApi" + } +} + +tasks.withType { + dokkaSourceSets.named("main") { + samples.from("src/test/kotlin/samples/ExampleSpek.kt") + } +} + +tasks.withType { + useJUnitPlatform() + reports.junitXml.isEnabled = true +} + diff --git a/kotest/src/main/kotlin/KotestTestFilesAdapter.kt b/kotest/src/main/kotlin/KotestTestFilesAdapter.kt new file mode 100644 index 0000000..3433979 --- /dev/null +++ b/kotest/src/main/kotlin/KotestTestFilesAdapter.kt @@ -0,0 +1,40 @@ +package de.joshuagleitze.testfiles.kotest + +import de.joshuagleitze.testfiles.DefaultTestFiles +import io.kotest.core.listeners.TestListener +import io.kotest.core.spec.AutoScan +import io.kotest.core.spec.Spec +import io.kotest.core.test.TestCase +import io.kotest.core.test.TestResult +import io.kotest.core.test.TestStatus.Error +import io.kotest.core.test.TestStatus.Failure +import io.kotest.core.test.TestStatus.Ignored +import io.kotest.core.test.TestStatus.Success +import kotlin.reflect.KClass + +@AutoScan +internal object KotestTestFilesAdapter: TestListener { + override val name: String get() = "testfiles" + + override suspend fun prepareSpec(kclass: KClass) { + internalTestFiles.enterScope(kclass.qualifiedName ?: "") + } + + override suspend fun finalizeSpec(kclass: KClass, results: Map) { + internalTestFiles.leaveScope(results.values.map { convert(it) }.reduce { left, right -> left.combineWith(right) }) + } + + override suspend fun beforeAny(testCase: TestCase) { + internalTestFiles.enterScope(testCase.displayName) + } + + override suspend fun afterAny(testCase: TestCase, result: TestResult) { + internalTestFiles.leaveScope(convert(result)) + } + + private fun convert(result: TestResult) = when (result.status) { + Success -> DefaultTestFiles.TestResult.SUCCESS + Error, Failure -> DefaultTestFiles.TestResult.FAILURE + Ignored -> error("contact breach: kotest should not have called us!") + } +} diff --git a/kotest/src/main/kotlin/testFiles.kt b/kotest/src/main/kotlin/testFiles.kt new file mode 100644 index 0000000..74835cb --- /dev/null +++ b/kotest/src/main/kotlin/testFiles.kt @@ -0,0 +1,13 @@ +package de.joshuagleitze.testfiles.kotest + +import de.joshuagleitze.testfiles.DefaultTestFiles +import de.joshuagleitze.testfiles.TestFiles + +internal val internalTestFiles: DefaultTestFiles = DefaultTestFiles() + +/** + * A [TestFiles] instance that will use the structure of the Kotest tests in this project to create files. + * + * @sample de.joshuagleitze.testfiles.kotest.samples.ExampleSpek + */ +public val testFiles: TestFiles get() = internalTestFiles diff --git a/kotest/src/test/kotlin/SpekTestFilesIntegrationSpec.kt b/kotest/src/test/kotlin/SpekTestFilesIntegrationSpec.kt new file mode 100644 index 0000000..6713fa2 --- /dev/null +++ b/kotest/src/test/kotlin/SpekTestFilesIntegrationSpec.kt @@ -0,0 +1,39 @@ +package de.joshuagleitze.testfiles.kotest + +import ch.tutteli.atrium.api.fluent.en_GB.isDirectory +import ch.tutteli.atrium.api.fluent.en_GB.isReadable +import ch.tutteli.atrium.api.fluent.en_GB.isRegularFile +import ch.tutteli.atrium.api.fluent.en_GB.isWritable +import ch.tutteli.atrium.api.fluent.en_GB.parent +import ch.tutteli.atrium.api.fluent.en_GB.toBe +import ch.tutteli.atrium.api.verbs.expect +import ch.tutteli.atrium.core.polyfills.fullName +import de.joshuagleitze.testfiles.DefaultTestFiles +import io.kotest.core.spec.style.DescribeSpec +import kotlin.io.path.div + +class KotestTestFilesIntegrationSpec: DescribeSpec({ + val fileRoot = DefaultTestFiles.determineTestFilesRootDirectory() + + describe("testFiles") { + val expectedGroupFolder = fileRoot / "[${KotestTestFilesIntegrationSpec::class.fullName}]" / "[testFiles]" + + it("creates a test file with the appropriate name") { + expect(testFiles.createFile()) { + isRegularFile() + isReadable() + isWritable() + parent.toBe(expectedGroupFolder / "[creates a test file with the appropriate name]") + } + } + + it("creates a test directory with the appropriate name") { + expect(testFiles.createDirectory()) { + isDirectory() + isReadable() + isWritable() + parent.toBe(expectedGroupFolder / "[creates a test directory with the appropriate name]") + } + } + } +}) diff --git a/kotest/src/test/kotlin/samples/ExampleSpek.kt b/kotest/src/test/kotlin/samples/ExampleSpek.kt new file mode 100644 index 0000000..f8a6713 --- /dev/null +++ b/kotest/src/test/kotlin/samples/ExampleSpek.kt @@ -0,0 +1,22 @@ +package de.joshuagleitze.testfiles.kotest.samples + +import de.joshuagleitze.testfiles.DeletionMode.ALWAYS +import de.joshuagleitze.testfiles.DeletionMode.IF_SUCCESSFUL +import de.joshuagleitze.testfiles.DeletionMode.NEVER +import de.joshuagleitze.testfiles.kotest.testFiles +import io.kotest.core.spec.style.DescribeSpec + +class ExampleSpek: DescribeSpec({ + describe("using test files") { + it("generates file names") { + testFiles.createFile() + testFiles.createDirectory() + } + + it("cleans up files") { + testFiles.createFile("irrelevant", delete = ALWAYS) + testFiles.createFile("default mode", delete = IF_SUCCESSFUL) + testFiles.createFile("output", delete = NEVER) + } + } +}) diff --git a/spek/build.gradle.kts b/spek/build.gradle.kts index 7930adc..81b8e2d 100644 --- a/spek/build.gradle.kts +++ b/spek/build.gradle.kts @@ -8,6 +8,8 @@ plugins { id("org.jetbrains.dokka") } +val artifactId by extra("spek-testfiles") + dependencies { val spekVersion = "2.0.15" @@ -48,7 +50,7 @@ tasks.compileTestKotlin { tasks.withType { dokkaSourceSets.named("main") { - samples.from("src/test/kotlin/ExampleSpek.kt") + samples.from("src/test/kotlin/samples/ExampleSpek.kt") } } diff --git a/spek/src/main/kotlin/testFiles.kt b/spek/src/main/kotlin/testFiles.kt index 9d3fba2..adfa5be 100644 --- a/spek/src/main/kotlin/testFiles.kt +++ b/spek/src/main/kotlin/testFiles.kt @@ -10,6 +10,6 @@ import org.spekframework.spek2.dsl.Root * This function _must_ be called from the root of a Spek before any test is created. That means, for example, that it _must not_ be called * from the initializer of a `memoized` value. The returned instance should be used throughout the Spek. * - * @sample de.joshuagleitze.testfiles.spek.ExampleSpek + * @sample de.joshuagleitze.testfiles.spek.samples.ExampleSpek */ public fun Root.testFiles(): TestFiles = DefaultTestFiles().also { registerListener(SpekTestFilesAdapter(it)) } diff --git a/spek/src/test/kotlin/ExampleSpek.kt b/spek/src/test/kotlin/samples/ExampleSpek.kt similarity index 87% rename from spek/src/test/kotlin/ExampleSpek.kt rename to spek/src/test/kotlin/samples/ExampleSpek.kt index 52f5779..88fcfd4 100644 --- a/spek/src/test/kotlin/ExampleSpek.kt +++ b/spek/src/test/kotlin/samples/ExampleSpek.kt @@ -1,8 +1,9 @@ -package de.joshuagleitze.testfiles.spek +package de.joshuagleitze.testfiles.spek.samples import de.joshuagleitze.testfiles.DeletionMode.ALWAYS import de.joshuagleitze.testfiles.DeletionMode.IF_SUCCESSFUL import de.joshuagleitze.testfiles.DeletionMode.NEVER +import de.joshuagleitze.testfiles.spek.testFiles import org.spekframework.spek2.Spek import org.spekframework.spek2.style.specification.describe diff --git a/src/main/kotlin/DefaultTestFiles.kt b/src/main/kotlin/DefaultTestFiles.kt index fb6b50a..953aa05 100644 --- a/src/main/kotlin/DefaultTestFiles.kt +++ b/src/main/kotlin/DefaultTestFiles.kt @@ -167,26 +167,26 @@ public class DefaultTestFiles: TestFiles { public enum class TestResult { SUCCESS { - override fun combineWith(otherResult: TestResult) = when (otherResult) { + public override fun combineWith(otherResult: TestResult): TestResult = when (otherResult) { SUCCESS -> SUCCESS FAILURE -> FAILURE } - override fun shouldBeDeleted(deletionMode: DeletionMode) = when (deletionMode) { + public override fun shouldBeDeleted(deletionMode: DeletionMode): Boolean = when (deletionMode) { ALWAYS, IF_SUCCESSFUL -> true NEVER -> false } }, FAILURE { - override fun combineWith(otherResult: TestResult) = FAILURE - override fun shouldBeDeleted(deletionMode: DeletionMode) = when (deletionMode) { + public override fun combineWith(otherResult: TestResult): TestResult = FAILURE + public override fun shouldBeDeleted(deletionMode: DeletionMode): Boolean = when (deletionMode) { ALWAYS -> true IF_SUCCESSFUL, NEVER -> false } }; - internal abstract fun combineWith(otherResult: TestResult): TestResult - internal abstract fun shouldBeDeleted(deletionMode: DeletionMode): Boolean + public abstract fun combineWith(otherResult: TestResult): TestResult + public abstract fun shouldBeDeleted(deletionMode: DeletionMode): Boolean } }