From d376becf2e99300e69d5f3c9a39ef9baa97bfe83 Mon Sep 17 00:00:00 2001 From: Mirko Felice Date: Tue, 20 Jun 2023 16:01:03 +0200 Subject: [PATCH] refactor: use testkit library to test plugin --- build.gradle.kts | 17 +++- gradle/libs.versions.toml | 5 +- .../danilopianini/gradle/test/TestingDSL.kt | 76 -------------- .../org/danilopianini/gradle/test/Tests.kt | 99 +++++-------------- .../danilopianini/gradle/test/ktjs/test.yaml | 31 +++--- .../gradle/test/ktmultiplatform/test.yaml | 38 ++++--- .../gradle/test/multiproject/test.yaml | 22 +++-- .../danilopianini/gradle/test/test0/test.yaml | 92 +++++++++-------- 8 files changed, 149 insertions(+), 231 deletions(-) delete mode 100644 src/test/kotlin/org/danilopianini/gradle/test/TestingDSL.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2be3f26f..b06096d9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,10 +46,25 @@ dependencies { api(libs.nexus.publish) implementation(libs.kotlinx.coroutines) implementation(libs.fuel) - testImplementation(gradleTestKit()) + testImplementation(libs.testkit) testImplementation(libs.bundles.kotlin.testing) } +/* + Following lines from 59-66 are needed as a workaround because of an official Gradle issue, + linked to https://github.com/gradle/gradle/issues/16603. + The issue is related to Gradle Daemon that is closed automatically by the Testkit official library, + but jacoco agent does not wait for it. +*/ +inline fun Project.disableTrackState() { + tasks.withType().configureEach { + doNotTrackState("Otherwise JaCoCo does not work correctly") + } +} + +disableTrackState() +disableTrackState() + tasks.withType { kotlinOptions { allWarningsAsErrors = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ede90396..5492be23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,18 +3,19 @@ dokka = "1.8.20" kotest = "5.6.2" kotlin = "1.8.22" nexus-publish = "1.3.0" +testkit = "0.8.4" [libraries] fuel = "com.github.kittinunf.fuel:fuel:2.3.1" -konf-yaml = "com.uchuhimo:konf-yaml:1.1.2" kotest-junit5-jvm = { module = "io.kotest:kotest-runner-junit5-jvm", version.ref = "kotest" } kotest-assertions-core-jvm = { module = "io.kotest:kotest-assertions-core-jvm", version.ref = "kotest" } kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlinx-coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1" nexus-publish = { module = "io.github.gradle-nexus:publish-plugin", version.ref = "nexus-publish" } +testkit = { module = "io.github.mirko-felice.testkit:core", version.ref = "testkit" } [bundles] -kotlin-testing = [ "kotest-junit5-jvm", "kotest-assertions-core-jvm", "konf-yaml" ] +kotlin-testing = [ "kotest-junit5-jvm", "kotest-assertions-core-jvm" ] [plugins] dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } diff --git a/src/test/kotlin/org/danilopianini/gradle/test/TestingDSL.kt b/src/test/kotlin/org/danilopianini/gradle/test/TestingDSL.kt deleted file mode 100644 index e3611b94..00000000 --- a/src/test/kotlin/org/danilopianini/gradle/test/TestingDSL.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.danilopianini.gradle.test - -import com.uchuhimo.konf.ConfigSpec -import org.gradle.internal.impldep.org.apache.commons.lang.StringUtils -import java.io.File - -object Root : ConfigSpec("") { - val tests by required>() -} - -data class Test( - val description: String, - val configuration: Configuration, - val expectation: Expectation, -) - -data class Configuration(val tasks: List, val options: List = emptyList()) - -@Suppress("ConstructorParameterNaming") -data class Expectation( - val file_exists: List = emptyList(), - val success: List = emptyList(), - val failure: List = emptyList(), - val not_executed: List = emptyList(), - val output_contains: List = emptyList(), - val output_doesnt_contain: List = emptyList(), -) - -enum class Permission(private val hasPermission: File.() -> Boolean) { - R(File::canRead), W(File::canWrite), X(File::canExecute); - - fun requireOnFile(file: File) = require(file.hasPermission()) { - "File ${file.absolutePath} must have permission $name, but it does not." - } -} - -data class ExistingFile( - val name: String, - val findRegex: List = emptyList(), - val content: String? = null, - val trim: Boolean = false, - val permissions: List = emptyList(), -) { - fun validate(actualFile: File): Unit = with(actualFile) { - require(exists()) { - "File $name does not exist." - } - if (content != null) { - val text = readText() - require(text == content) { - """ - Content of $name does not match expectations. - - Expected: - $content - - Actual: - $text - - Difference starts at index ${StringUtils.indexOfDifference(content, text)}: - ${StringUtils.difference(content, text)} - """.trimIndent() - } - } - findRegex.forEach { regexString -> - val regex = Regex(regexString) - requireNotNull(readLines().find { regex.matches(it) }) { - """ - None of the lines in $name matches the regular expression $regex. File content: - ${readText()} - """.trimIndent() - } - } - permissions.forEach { it.requireOnFile(this) } - } -} diff --git a/src/test/kotlin/org/danilopianini/gradle/test/Tests.kt b/src/test/kotlin/org/danilopianini/gradle/test/Tests.kt index 34c29487..a7c89737 100644 --- a/src/test/kotlin/org/danilopianini/gradle/test/Tests.kt +++ b/src/test/kotlin/org/danilopianini/gradle/test/Tests.kt @@ -1,85 +1,30 @@ package org.danilopianini.gradle.test -import com.uchuhimo.konf.Config -import com.uchuhimo.konf.source.yaml -import io.github.classgraph.ClassGraph +import io.github.mirkofelice.api.Testkit import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.file.shouldBeAFile -import io.kotest.matchers.file.shouldExist -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.shouldContain -import io.kotest.matchers.string.shouldNotContain -import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.slf4j.Logger -import org.slf4j.LoggerFactory import java.io.File -class Tests : StringSpec( - { - val scan = ClassGraph() - .enableAllInfo() - .acceptPackages(Tests::class.java.`package`.name) - .scan() - scan.getResourcesWithLeafName("test.yaml") - .flatMap { resource -> - log.debug("Found test list in {}", resource) - val yamlFile = File(resource.classpathElementFile.absolutePath + "/" + resource.path) - val testConfiguration = Config { addSpec(Root) }.from.yaml.inputStream(resource.open()) - testConfiguration[Root.tests].map { it to yamlFile.parentFile } - } - .forEach { (test, location) -> - log.debug("Test to be executed: {} from {}", test, location) - val testFolder = folder { - location.copyRecursively(this.root) - } - log.debug("Test has been copied into {} and is ready to get executed", testFolder) - test.description { - val result = GradleRunner.create() - .withProjectDir(testFolder.root) - .withPluginClasspath() - .withArguments(test.configuration.tasks + test.configuration.options) - .run { if (test.expectation.failure.isEmpty()) build() else buildAndFail() } - println(result.tasks) - println(result.output) - test.expectation.output_contains.forEach { - result.output shouldContain it - } - test.expectation.output_doesnt_contain.forEach { - result.output shouldNotContain it - } - test.expectation.success.forEach { - result.outcomeOf(it) shouldBe TaskOutcome.SUCCESS - } - test.expectation.failure.forEach { - result.outcomeOf(it) shouldBe TaskOutcome.FAILED - } - test.expectation.not_executed.forEach { - result.task(it) shouldBe null - } - test.expectation.file_exists.forEach { - val file = File("${testFolder.root.absolutePath}/${it.name}").apply { - shouldExist() - shouldBeAFile() - } - it.validate(file) - } - } - } - }, -) { - companion object { - val log: Logger = LoggerFactory.getLogger(Tests::class.java) +class Tests : StringSpec({ - private fun BuildResult.outcomeOf(name: String) = checkNotNull(task(":$name")?.outcome) { - "Task $name was not present among the executed tasks" - } + val projectName = "publish-on-central" + val sep = File.separator + val baseFolder = Testkit.DEFAULT_TEST_FOLDER + "org${sep}danilopianini${sep}gradle${sep}test$sep" - private fun folder(closure: TemporaryFolder.() -> Unit) = TemporaryFolder().apply { - create() - closure() - } + fun Testkit.projectTest(folder: String) = this.test(projectName, baseFolder + folder) + + "Test ktjs" { + Testkit.projectTest("ktjs") + } + + "Test ktmultiplatform" { + Testkit.projectTest("ktmultiplatform") + } + + "Test multiproject" { + Testkit.projectTest("multiproject") + } + + "Test test0" { + Testkit.projectTest("test0") } -} +}) diff --git a/src/test/resources/org/danilopianini/gradle/test/ktjs/test.yaml b/src/test/resources/org/danilopianini/gradle/test/ktjs/test.yaml index 4e07ce1b..f814049b 100644 --- a/src/test/resources/org/danilopianini/gradle/test/ktjs/test.yaml +++ b/src/test/resources/org/danilopianini/gradle/test/ktjs/test.yaml @@ -6,9 +6,11 @@ tests: options: - '--stacktrace' expectation: - success: *tasks1 - file_exists: &exists - - name: /build/libs/ktjs-1.0.0-sources.jar + outcomes: + success: *tasks1 + files: + existing: &exists + - name: /build/libs/ktjs-1.0.0-sources.jar - description: "Task :jsSourcesJar should generate sources Jar with for Kotlin Js projects" configuration: tasks: &tasks2 @@ -16,8 +18,10 @@ tests: options: - '--stacktrace' expectation: - success: *tasks2 - file_exists: *exists + outcomes: + success: *tasks2 + files: + existing: *exists - description: "Task :kotlinSourcesJar should generate sources Jar with for Kotlin Js projects" configuration: tasks: &tasks3 @@ -25,8 +29,10 @@ tests: options: - '--stacktrace' expectation: - success: *tasks3 - file_exists: *exists + outcomes: + success: *tasks3 + files: + existing: *exists - description: "Publishing should only use :jsSourcesJar to create sources Jars" configuration: tasks: @@ -35,8 +41,9 @@ tests: - '--dry-run' - '--stacktrace' expectation: - output_contains: - - ":kotlinSourcesJar SKIPPED" - output_doesnt_contain: - - ":sourcesJar" - - ":jsSourcesJar" \ No newline at end of file + output: + contains: + - ":kotlinSourcesJar SKIPPED" + doesntContain: + - ":sourcesJar" + - ":jsSourcesJar" \ No newline at end of file diff --git a/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml b/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml index 1164a85b..54778b78 100644 --- a/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml +++ b/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml @@ -10,18 +10,22 @@ tests: options: - '--stacktrace' expectation: - success: *tasks + outcomes: + success: *tasks - description: "gradle should generate nexus lifecycle management tasks in multiplatform projects" configuration: tasks: - tasks expectation: - success: tasks - output_contains: - - releaseStagingRepositoryOnMavenCentral - - createStagingRepositoryOnMavenCentral - - closeStagingRepositoryOnMavenCentral - - uploadAllPublicationsToMavenCentralNexus + outcomes: + success: + - tasks + output: + contains: + - releaseStagingRepositoryOnMavenCentral + - createStagingRepositoryOnMavenCentral + - closeStagingRepositoryOnMavenCentral + - uploadAllPublicationsToMavenCentralNexus - description: "dokkaHtml works" configuration: tasks: @@ -29,7 +33,9 @@ tests: options: - '--stacktrace' expectation: - success: dokkaHtml + outcomes: + success: + - dokkaHtml - description: "javadocJar should rely on dokkaHtml instead of dokkaJavadoc" configuration: tasks: @@ -38,9 +44,13 @@ tests: - '--info' - '--stacktrace' expectation: - success: dokkaHtml - not_executed: dokkaJavadoc - output_contains: - - "Dokka plugin found, hence javadocJar will be configured" - - "Lazily configure javadocJar task to depend on Dokka task" - - "Actually configure javadocJar task to depend on Dokka task dokkaHtml" + outcomes: + success: + - dokkaHtml + notExecuted: + - dokkaJavadoc + output: + contains: + - "Dokka plugin found, hence javadocJar will be configured" + - "Lazily configure javadocJar task to depend on Dokka task" + - "Actually configure javadocJar task to depend on Dokka task dokkaHtml" diff --git a/src/test/resources/org/danilopianini/gradle/test/multiproject/test.yaml b/src/test/resources/org/danilopianini/gradle/test/multiproject/test.yaml index f4c35c6e..3b678a76 100644 --- a/src/test/resources/org/danilopianini/gradle/test/multiproject/test.yaml +++ b/src/test/resources/org/danilopianini/gradle/test/multiproject/test.yaml @@ -7,13 +7,15 @@ tests: - '--all' - '--stacktrace' expectation: - success: *tasks - output_contains: - - releaseStagingRepositoryOnMavenCentral - - createStagingRepositoryOnMavenCentral - - closeStagingRepositoryOnMavenCentral - - uploadAllPublicationsToMavenCentralNexus - output_doesnt_contain: - - :releaseStagingRepositoryOnMavenCentral - - :createStagingRepositoryOnMavenCentral - - :closeStagingRepositoryOnMavenCentral + outcomes: + success: *tasks + output: + contains: + - releaseStagingRepositoryOnMavenCentral + - createStagingRepositoryOnMavenCentral + - closeStagingRepositoryOnMavenCentral + - uploadAllPublicationsToMavenCentralNexus + doesntContain: + - :releaseStagingRepositoryOnMavenCentral + - :createStagingRepositoryOnMavenCentral + - :closeStagingRepositoryOnMavenCentral diff --git a/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml b/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml index 1beb62df..7a797267 100644 --- a/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml +++ b/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml @@ -1,44 +1,56 @@ tests: - description: "pom files get generated" configuration: - tasks: generatePomFileForJavaOSSRHPublication + tasks: + - generatePomFileForJavaOSSRHPublication expectation: - success: generatePomFileForJavaOSSRHPublication - file_exists: - name: build/publications/javaOSSRH/pom-default.xml - findRegex: - - '\s*test-publish-on-central' - - '\s*io.github.danysk' - - '\s*test-publish-on-central' - - '\s*\w+' - - '\s*https?://.+' - - '\s*' - - '\s*' - - '\s*' - - '\s*(\s|\n)*scm:git:https://github\.com/test/test-publish-on-central' - - '\s*(\s|\n)*scm:git:https://github\.com/test/test-publish-on-central' - - '\s*' + outcomes: + success: + - generatePomFileForJavaOSSRHPublication + files: + existing: + - name: build/publications/javaOSSRH/pom-default.xml + contentRegex: + - '\s*test-publish-on-central' + - '\s*io.github.danysk' + - '\s*test-publish-on-central' + - '\s*\w+' + - '\s*https?://.+' + - '\s*' + - '\s*' + - '\s*' + - '\s*(\s|\n)*scm:git:https://github\.com/test/test-publish-on-central' + - '\s*(\s|\n)*scm:git:https://github\.com/test/test-publish-on-central' + - '\s*' - description: "release and drop tasks get generated" configuration: - tasks: tasks + tasks: + - tasks expectation: - success: tasks - output_contains: - - publishPluginMavenPublicationToGithubRepository - - uploadJavaOSSRHToMavenCentralNexus - - uploadPluginMavenToMavenCentralNexus - - releaseStagingRepositoryOnMavenCentral - - dropStagingRepositoryOnMavenCentral - output_doesnt_contain: - - uploadJavaMavenToGithubNexus + outcomes: + success: + - tasks + output: + contains: + - publishPluginMavenPublicationToGithubRepository + - uploadJavaOSSRHToMavenCentralNexus + - uploadPluginMavenToMavenCentralNexus + - releaseStagingRepositoryOnMavenCentral + - dropStagingRepositoryOnMavenCentral + doesntContain: + - uploadJavaMavenToGithubNexus - description: "sources and javadoc tasks get greated" configuration: - tasks: tasks + tasks: + - tasks expectation: - success: tasks - output_contains: - - sourcesJar - - javadocJar + outcomes: + success: + - tasks + output: + contains: + - sourcesJar + - javadocJar - description: "sources and javadoc jars get created" configuration: tasks: @@ -46,11 +58,13 @@ tests: - sourcesJar - javadocJar expectation: - success: - - jar - - sourcesJar - - javadocJar - file_exists: - - name: build/libs/test-publish-on-central-0.1.0-javadoc.jar - - name: build/libs/test-publish-on-central-0.1.0-sources.jar - - name: build/libs/test-publish-on-central-0.1.0.jar + outcomes: + success: + - jar + - sourcesJar + - javadocJar + files: + existing: + - name: build/libs/test-publish-on-central-0.1.0-javadoc.jar + - name: build/libs/test-publish-on-central-0.1.0-sources.jar + - name: build/libs/test-publish-on-central-0.1.0.jar