From c8f39e7344adbbceb672547b69724a4f33164414 Mon Sep 17 00:00:00 2001 From: Ivan “CLOVIS” Canet Date: Sun, 22 Oct 2023 13:10:45 +0000 Subject: [PATCH] merge: Gradle TestKit compatibility Closes #22 See merge request opensavvy/prepared!29 --- build.gradle.kts | 1 + settings.gradle.kts | 1 + gradle/libs.versions.toml | 2 ++ compat/compat-gradle/README.jvm.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ compat/compat-gradle/build.gradle.kts | 30 ++++++++++++++++++++++++++++++ compat/compat-gradle/src/jvmMain/kotlin/Gradle.kt | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ compat/compat-gradle/src/jvmMain/kotlin/Paths.kt | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ compat/compat-gradle/src/jvmMain/kotlin/Project.kt | 36 ++++++++++++++++++++++++++++++++++++ 8 file(s) changed, 391 insertion(s)(+), 0 deletion(s)(-) diff --git a/build.gradle.kts b/build.gradle.kts --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ dokkatoo(projects.runners.runnerKotest) dokkatoo(projects.runners.runnerKotlinTest) dokkatoo(projects.framework) + dokkatoo(projects.compat.compatGradle) dokkatoo(projects.compat.compatKotlinxDatetime) dokkatoo(projects.compat.compatFilesystem) diff --git a/settings.gradle.kts b/settings.gradle.kts --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,6 +35,7 @@ "framework", "compat:compat-kotlinx-datetime", + "compat:compat-gradle", "compat:compat-filesystem", "runners:runner-kotlin-test", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ kotest = "5.7.1" kotlinx-coroutines = "1.7.3" kotlinx-datetime = "0.4.1" +gradle-testkit = "8.4" [plugins] kotest = { id = "io.kotest.multiplatform", version.ref = "kotest" } @@ -20,5 +21,6 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotest-engine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } +gradle-testkit = { module = "dev.gradleplugins:gradle-test-kit", version.ref = "gradle-testkit" } [bundles] diff --git a/compat/compat-gradle/README.jvm.md b/compat/compat-gradle/README.jvm.md new file mode 100644 --- /dev/null +++ b/compat/compat-gradle/README.jvm.md @@ -0,0 +1,44 @@ +# Module Compatibility with Gradle TestKit + +Write tests for Gradle plugins easily. + + + +This module creates the `gradle` extension point that automatically creates a temporary directory in which the Gradle build is created. + +```kotlin +test("A test that uses Gradle") { + // Create Kotlin or Grovvy DSL files directly… + gradle.settingsKts(""" + include("foo") + """.trimIndent()) + + gradle.rootProject.buildKts(""" + tasks.register("test") { + doLast { + println("Testing the root project") + } + } + """.trimIndent()) + + // …create multi-project builds easily… + gradle.project("foo").buildKts(""" + tasks.register("test") { + doLast { + println("Testing the :foo project") + } + } + """.trimIndent()) + + // …start a Gradle instance in the related project… + val result = gradle.runner() + .withArguments("test") + .build() + + // …assert that the output is as expected. + result.output shouldContain "Testing the root project" + result.output shouldContain "Testing the :foo project" +} +``` + +To learn more, see the [gradle][opensavvy.prepared.compat.gradle.gradle] extension point. diff --git a/compat/compat-gradle/build.gradle.kts b/compat/compat-gradle/build.gradle.kts new file mode 100644 --- /dev/null +++ b/compat/compat-gradle/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + id("conventions.base") + id("conventions.kotlin") + id("conventions.library") +} + +kotlin { + jvm() + + val commonMain by sourceSets.getting { + dependencies { + api(projects.suite) + api(projects.compat.compatFilesystem) + + api(libs.gradle.testkit) + } + } + + val commonTest by sourceSets.getting { + dependencies { + implementation(projects.framework) + } + } +} + +library { + name.set("Compatibility with Gradle TestKit") + description.set("Test Gradle plugins using Prepared") + homeUrl.set("https://opensavvy.gitlab.io/prepared/documentation/compat-gradle/index.html") +} diff --git a/compat/compat-gradle/src/jvmMain/kotlin/Gradle.kt b/compat/compat-gradle/src/jvmMain/kotlin/Gradle.kt new file mode 100644 --- /dev/null +++ b/compat/compat-gradle/src/jvmMain/kotlin/Gradle.kt @@ -0,0 +1,117 @@ +package opensavvy.prepared.compat.gradle + +import opensavvy.prepared.compat.filesystem.createRandomDirectory +import opensavvy.prepared.compat.filesystem.div +import opensavvy.prepared.suite.TestDsl +import org.gradle.testkit.runner.GradleRunner +import kotlin.io.path.ExperimentalPathApi + +@OptIn(ExperimentalPathApi::class) +private val rootProjectDir by createRandomDirectory("gradle-testkit-") + +/** + * Control center for Gradle TestKit. See [gradle]. + */ +class Gradle internal constructor( + internal val dsl: TestDsl, +) { + + /** + * A temporary directory unique for each test, in which the Gradle files are created. + * + * ### Example + * + * Print the directory: + * ```kotlin + * test("In which directory does Gradle execute?") { + * println(gradle.dir()) + * } + * ``` + */ + val dir get() = rootProjectDir + + /** + * Accessor for the files of the root project. + * + * ### Example + * + * Create the root `build.gradle.kts` file: + * ```kotlin + * test("Create the root build.gradle.kts file") { + * gradle.rootProject.buildKts(""" + * println("Configuring the project") + * """.trimIndent() + * } + * ``` + * + * @see project Access another project + * @see runner Execute the test and check the outputs + */ + val rootProject get() = Project(this, dir) + + /** + * Accessor for the files of a project, given its [path]. + * + * ### Example + * + * Create the `modules/foo/build.gradle` file: + * ```kotlin + * test("Configure the :modules:foo project") { + * gradle.project("modules/foo").buildGroovy(""" + * println "Configuring the project!" + * """.trimIndent()) + * } + * ``` + * + * @see rootProject Access the root project + * @see runner Execute the test and check the outputs + */ + fun project(path: String) = Project(this, dir / path) + + /** + * Instantiates a [GradleRunner] in [dir]. + * + * ### Examples + * + * ```kotlin + * test("Create the root build.gradle.kts file") { + * gradle.rootProject.buildKts(""" + * tasks.register("print") { + * doLast { + * println("Configuring the project") + * } + * } + * """.trimIndent() + * + * val result = gradle.runner() + * .withPluginClasspath() + * .withArguments("print") + * .build() + * + * result.output shouldContain "Configuring the project" + * } + * ``` + * + * @see GradleRunner.withPluginClasspath When writing tests for a plugin, automatically adds it to the executed Gradle instance + * @see GradleRunner.withArguments Specify which tasks should be executed + * @see GradleRunner.build Executes the build, expecting a success + * @see GradleRunner.buildAndFail Executes the build, expecting a failure + */ + suspend fun runner(): GradleRunner = with(dsl) { + GradleRunner.create() + .withProjectDir(dir().toFile()) + } +} + +/** + * Control center for [Gradle TestKit](https://docs.gradle.org/current/userguide/test_kit.html). + * + * Each test is assigned a temporary directory in which Gradle can be configured (see [dir][Gradle.dir]). + * Usually, tests will: + * 1. Create the settings file (see [settingsGroovy] or [settingsKts]), + * 2. Create the build script files (see [buildGroovy] or [buildKts]), + * 3. Execute Gradle (see [runner][Gradle.runner]) + * 4. Make assertions on the result. + */ +val TestDsl.gradle: Gradle + get() = Gradle(this) diff --git a/compat/compat-gradle/src/jvmMain/kotlin/Paths.kt b/compat/compat-gradle/src/jvmMain/kotlin/Paths.kt new file mode 100644 --- /dev/null +++ b/compat/compat-gradle/src/jvmMain/kotlin/Paths.kt @@ -0,0 +1,160 @@ +package opensavvy.prepared.compat.gradle + +import opensavvy.prepared.compat.filesystem.div +import org.intellij.lang.annotations.Language +import kotlin.io.path.writeText + +// region Build + +/** + * Accessor for the `settings.gradle` file. + * + * ### Example + * + * ```kotlin + * test("Access the groovy settings file") { + * println(gradle.settingsGroovy()) + * } + * ``` + * + * @see settingsKts Kotlin equivalent + * @see buildGroovy Build script file + */ +val Gradle.settingsGroovy get() = dir / "settings.gradle" + +/** + * Accessor for the `settings.gradle.kts` file. + * + * ### Example + * + * ```kotlin + * test("Access the Kotlin DSL settings file") { + * println(gradle.settingsKts()) + * } + * ``` + * + * @see settingsGroovy Groovy equivalent + * @see buildKts Build script file + */ +val Gradle.settingsKts get() = dir / "settings.gradle.kts" + +/** + * Helper function to write the `settings.gradle` file. + * + * ### Example + * + * ```kotlin + * test("Create a groovy settings file") { + * gradle.settingsGroovy(""" + * println "Loading the settings…" + * """.trimIndent()) + * } + * ``` + * + * @see settingsKts Kotlin equivalent + * @see buildGroovy Build script file + */ +suspend fun Gradle.settingsGroovy(@Language("groovy") text: String) = with(dsl) { + settingsGroovy().writeText(text) +} + +/** + * Helper function to write the `settings.gradle.kts` file. + * + * ### Example + * + * ```kotlin + * test("Create a Kotlin DSL settings file") { + * gradle.settingsKts(""" + * println("Loading the settings…") + * """.trimIndent()) + * } + * ``` + * + * @see settingsGroovy Groovy equivalent + * @see buildKts Build script file + */ +suspend fun Gradle.settingsKts(@Language("kts") text: String) = with(dsl) { + settingsKts().writeText(text) +} + +// endregion +// region Project + +/** + * Accessor for the `build.gradle` file. + * + * ### Example + * + * ```kotlin + * test("Access the groovy build file") { + * println(gradle.project("foo").buildGroovy()) + * } + * ``` + * + * @see buildKts Kotlin equivalent + * @see settingsGroovy Settings file + * @see Gradle.project Select the project + */ +val Project.buildGroovy get() = dir / "build.gradle" + +/** + * Accessor for the `build.gradle.kts` file. + * + * ### Example + * + * ```kotlin + * test("Access the Kotlin build file") { + * println(gradle.project("foo").buildKts()) + * } + * ``` + * + * @see buildGroovy Groovy equivalent + * @see settingsKts Settings file + * @see Gradle.project Select the project + */ +val Project.buildKts get() = dir / "build.gradle.kts" + +/** + * Helper function to write the `build.gradle` file. + * + * ### Example + * + * ```kotlin + * test("Create a groovy build file") { + * gradle.project("foo").buildGroovy(""" + * println "Loading the project :foo…" + * """.trimIndent()) + * } + * ``` + * + * @see buildKts Kotlin equivalent + * @see settingsGroovy Settings file + * @see Gradle.project Select the project + */ +suspend fun Project.buildGroovy(@Language("groovy") text: String) = with(dsl) { + buildGroovy().writeText(text) +} + +/** + * Helper function to write the `build.gradle.kts` file. + * + * ### Example + * + * ```kotlin + * test("Create a Kotlin build file") { + * gradle.project("foo").buildKts(""" + * println("Loading the project :foo…") + * """.trimIndent()) + * } + * ``` + * + * @see buildGroovy Groovy equivalent + * @see settingsKts Settings file + * @see Gradle.project Select the project + */ +suspend fun Project.buildKts(@Language("kts") text: String) = with(dsl) { + buildKts().writeText(text) +} + +// endregion diff --git a/compat/compat-gradle/src/jvmMain/kotlin/Project.kt b/compat/compat-gradle/src/jvmMain/kotlin/Project.kt new file mode 100644 --- /dev/null +++ b/compat/compat-gradle/src/jvmMain/kotlin/Project.kt @@ -0,0 +1,36 @@ +package opensavvy.prepared.compat.gradle + +import opensavvy.prepared.compat.filesystem.div +import opensavvy.prepared.suite.Prepared +import java.nio.file.Path + +/** + * Represents a Gradle project. + * + * To access an instance of this type, use [Gradle.rootProject] or [Gradle.project]. + */ +class Project internal constructor( + private val build: Gradle, + + /** + * The subdirectory of [Gradle.dir] in which this project is located. + * + * ### Example + * + * ```kotlin + * test("Print the project structure") { + * println(gradle.dir()) + * println(gradle.project("foo").dir()) + * } + * ``` + */ + val dir: Prepared, +) { + + internal val dsl get() = build.dsl + + /** + * The `build` directory for this [Project]. + */ + val buildDir get() = dir / "build" +} -- tangled.sh