Skip to content

Commit

Permalink
Update Maven Integration tests to use JVM test suites
Browse files Browse the repository at this point in the history
Part of KT-64200

WIP
  • Loading branch information
adam-enko committed Apr 23, 2024
1 parent 6ca63aa commit 8b6dba6
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 92 deletions.
144 changes: 72 additions & 72 deletions build-logic/src/main/kotlin/dokkabuild.test-integration.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,72 +1,72 @@
/*
* Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
id("dokkabuild.kotlin-jvm")
}

val integrationTestSourceSet: SourceSet = sourceSets.create("integrationTest") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}

val integrationTestImplementation: Configuration by configurations.getting {
extendsFrom(configurations.implementation.get())
}

val integrationTestRuntimeOnly: Configuration by configurations.getting {
extendsFrom(configurations.runtimeOnly.get())
}

/**
* Dokka's integration test task is not cacheable because the HTML outputs
* it produces when running the tests are used for showcasing resulting documentation,
* which does not work well with caching.
*
* At the moment there are two problems that do not allow to make it cacheable:
*
* 1. The task's inputs are such that changes in Dokka's code do not invalidate the cache,
* because it is run with the same version of Dokka (`"DOKKA_VERSION"`) on the same
* test project inputs.
* 2. The tests generate HTML output which is then used to showcase documentation.
* The outputs are usually copied to a location from which it will be served.
* However, if the test is cacheable, it produces no outputs, so no documentation
* to showcase. It needs to be broken into two separate tasks: one cacheable for running
* the tests and producing HTML output, and another non-cacheable for copying the output.
*
* @see [org.jetbrains.dokka.it.TestOutputCopier] for more details on showcasing documentation
*/
@DisableCachingByDefault(because = "Contains incorrect inputs/outputs configuration, see the KDoc for details")
abstract class NonCacheableIntegrationTest : Test()

val integrationTest by tasks.registering(NonCacheableIntegrationTest::class) {
maxHeapSize = "2G"
description = "Runs integration tests."
group = "verification"
testClassesDirs = integrationTestSourceSet.output.classesDirs
classpath = integrationTestSourceSet.runtimeClasspath

useJUnitPlatform {
if (dokkaBuild.integrationTestUseK2.get()) excludeTags("onlyDescriptors", "onlyDescriptorsMPP")
}

systemProperty("org.jetbrains.dokka.experimental.tryK2", dokkaBuild.integrationTestUseK2.get())

dokkaBuild.integrationTestParallelism.orNull?.let { parallelism ->
maxParallelForks = parallelism
}

environment("isExhaustive", dokkaBuild.integrationTestExhaustive.get())

testLogging {
exceptionFormat = TestExceptionFormat.FULL
events(TestLogEvent.SKIPPED, TestLogEvent.FAILED)
showExceptions = true
showCauses = true
showStackTraces = true
}
}
///*
// * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
// */
//
//import org.gradle.api.tasks.testing.logging.TestExceptionFormat
//import org.gradle.api.tasks.testing.logging.TestLogEvent
//
//plugins {
// id("dokkabuild.kotlin-jvm")
//}
//
//val integrationTestSourceSet: SourceSet = sourceSets.create("integrationTest") {
// compileClasspath += sourceSets.main.get().output
// runtimeClasspath += sourceSets.main.get().output
//}
//
//val integrationTestImplementation: Configuration by configurations.getting {
// extendsFrom(configurations.implementation.get())
//}
//
//val integrationTestRuntimeOnly: Configuration by configurations.getting {
// extendsFrom(configurations.runtimeOnly.get())
//}
//
///**
// * Dokka's integration test task is not cacheable because the HTML outputs
// * it produces when running the tests are used for showcasing resulting documentation,
// * which does not work well with caching.
// *
// * At the moment there are two problems that do not allow to make it cacheable:
// *
// * 1. The task's inputs are such that changes in Dokka's code do not invalidate the cache,
// * because it is run with the same version of Dokka (`"DOKKA_VERSION"`) on the same
// * test project inputs.
// * 2. The tests generate HTML output which is then used to showcase documentation.
// * The outputs are usually copied to a location from which it will be served.
// * However, if the test is cacheable, it produces no outputs, so no documentation
// * to showcase. It needs to be broken into two separate tasks: one cacheable for running
// * the tests and producing HTML output, and another non-cacheable for copying the output.
// *
// * @see [org.jetbrains.dokka.it.TestOutputCopier] for more details on showcasing documentation
// */
//@DisableCachingByDefault(because = "Contains incorrect inputs/outputs configuration, see the KDoc for details")
//abstract class NonCacheableIntegrationTest : Test()
//
//val integrationTest by tasks.registering(NonCacheableIntegrationTest::class) {
// maxHeapSize = "2G"
// description = "Runs integration tests."
// group = "verification"
// testClassesDirs = integrationTestSourceSet.output.classesDirs
// classpath = integrationTestSourceSet.runtimeClasspath
//
// useJUnitPlatform {
// if (dokkaBuild.integrationTestUseK2.get()) excludeTags("onlyDescriptors", "onlyDescriptorsMPP")
// }
//
// systemProperty("org.jetbrains.dokka.experimental.tryK2", dokkaBuild.integrationTestUseK2.get())
//
// dokkaBuild.integrationTestParallelism.orNull?.let { parallelism ->
// maxParallelForks = parallelism
// }
//
// environment("isExhaustive", dokkaBuild.integrationTestExhaustive.get())
//
// testLogging {
// exceptionFormat = TestExceptionFormat.FULL
// events(TestLogEvent.SKIPPED, TestLogEvent.FAILED)
// showExceptions = true
// showCauses = true
// showStackTraces = true
// }
//}
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,8 @@ fun includedBuildTasks(taskName: String, filter: (IncludedBuild) -> Boolean = {
.filter { it.name != "build-logic" }
.filter(filter)
.mapNotNull { it.task(":$taskName") }


tasks.check {
gradle.includedBuilds.forEach { dependsOn(it.task(":check")) }
}
1 change: 0 additions & 1 deletion dokka-integration-tests/gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ plugins {
id("dokkabuild.kotlin-jvm")
id("dokkabuild.dev-maven-publish")
`jvm-test-suite`
`java-test-fixtures`
}

dependencies {
Expand Down
203 changes: 187 additions & 16 deletions dokka-integration-tests/maven/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
/*
* Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UnstableApiUsage")

import dokkabuild.tasks.GitCheckoutTask
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP

plugins {
id("dokkabuild.test-integration")
id("dokkabuild.setup-maven-cli")
id("dokkabuild.kotlin-jvm")
id("dokkabuild.dev-maven-publish")
id("dokkabuild.setup-maven-cli")
`jvm-test-suite`
}

dependencies {
implementation(projects.utilities)
api(projects.utilities)

implementation(kotlin("test-junit5"))
implementation(libs.junit.jupiterApi)
api(kotlin("test-junit5"))
api(libs.junit.jupiterApi)

val dokkaVersion = project.version.toString()
// We're using Gradle included-builds and dependency substitution, so we
Expand Down Expand Up @@ -43,26 +50,190 @@ dependencies {

val templateProjectsDir = layout.projectDirectory.dir("projects")

val dokkaSubprojects = gradle.includedBuild("dokka")
val mavenPlugin = gradle.includedBuild("runner-maven-plugin")

tasks.integrationTest {
dependsOn(checkoutBioJava)
/**
* Provide files required for running Dokka CLI in a build cache friendly way.
*/
abstract class MvnBinaryPathArgProvider : CommandLineArgumentProvider {
@get:Classpath
abstract val maven: RegularFileProperty

dependsOn(tasks.installMavenBinary)
val mvn = mavenCliSetup.mvn
inputs.file(mvn)
override fun asArguments(): Iterable<String> = buildList {
add("-D" + "mavenBinaryFile=" + maven.asFile.get().absolutePath)
}
}


tasks.withType<Test>().configureEach {
setForkEvery(1)
maxHeapSize = "2G"
dokkaBuild.integrationTestParallelism.orNull?.let { parallelism ->
maxParallelForks = parallelism
}

val useK2 = dokkaBuild.integrationTestUseK2.get()

useJUnitPlatform {
if (useK2) excludeTags("onlyDescriptors", "onlyDescriptorsMPP")
}

systemProperty("org.jetbrains.dokka.experimental.tryK2", useK2)
// allow inspecting projects in temporary dirs after a test fails
systemProperty(
"junit.jupiter.tempdir.cleanup.mode.default",
if (dokkaBuild.isCI.get()) "ALWAYS" else "ON_SUCCESS",
)

val dokkaVersion = provider { project.version.toString() }
inputs.property("dokkaVersion", dokkaVersion)

doFirst("workaround for https://github.com/gradle/gradle/issues/24267") {
doFirst("set DOKKA_VERSION environment variable (workaround for https://github.com/gradle/gradle/issues/24267)") {
environment("DOKKA_VERSION", dokkaVersion.get())
environment("MVN_BINARY_PATH", mvn.get().asFile.invariantSeparatorsPath)
}

devMavenPublish.configureTask(this)
// environment() isn't Provider API compatible yet https://github.com/gradle/gradle/issues/11534
dokkaBuild.integrationTestExhaustive.orNull?.let { exhaustive ->
environment("isExhaustive", exhaustive)
}

dependsOn(tasks.installMavenBinary)
jvmArgumentProviders.add(
objects.newInstance<MvnBinaryPathArgProvider>().apply {
maven = mavenCliSetup.mvn
}
)

testLogging {
exceptionFormat = FULL
events(SKIPPED, FAILED)
showExceptions = true
showCauses = true
showStackTraces = true
}

// The tests produce report data and generated Dokka output.
// Always cache them so Gradle can skip running integration tests if nothing has changed.
outputs.cacheIf("always cache") { true }
}

testing {
suites {
withType<JvmTestSuite>().configureEach {
useJUnitJupiter()

dependencies {
// test suites are independent by default (unlike the test source set), and must manually depend on the project
implementation(project())
}

targets.configureEach {
testTask.configure {
doFirst {
logger.info("running $path with javaLauncher:${javaLauncher.orNull?.metadata?.javaRuntimeVersion}")
}
}
}
}

registerTestProjectSuite("testTemplateProjectMaven", "it-maven")
registerTestProjectSuite("testExternalProjectBioJava", "biojava/biojava") {
targets.configureEach {
testTask.configure {
dependsOn(checkoutBioJava)
// register the whole directory as an input because it contains the git diff
inputs
.dir(templateProjectsDir.file("biojava"))
.withPropertyName("biojavaProjectDir")
}
}
}
}
}

tasks.check {
dependsOn(testing.suites)
}

/**
* Create a new [JvmTestSuite] for a Gradle project.
*
* @param[projectPath] path to the Gradle project that will be tested by this suite, relative to [templateProjectsDir].
* The directory will be passed as a system property, `templateProjectDir`.
*/
fun TestingExtension.registerTestProjectSuite(
name: String,
projectPath: String,
jvm: JavaLanguageVersion? = null,
configure: JvmTestSuite.() -> Unit = {},
) {
val templateProjectDir = templateProjectsDir.dir(projectPath)

suites.register<JvmTestSuite>(name) {
targets.configureEach {
testTask.configure {
// Register the project dir as a specific input, so changes in other projects don't affect the caching of this test
inputs.dir(templateProjectDir)
.withPropertyName("templateProjectDir")
.withPathSensitivity(RELATIVE)

// Pass the template dir in as a property, it is accessible in tests.
systemProperty("templateProjectDir", templateProjectDir.asFile.invariantSeparatorsPath)

devMavenPublish.configureTask(this)

if (jvm != null) {
javaLauncher = javaToolchains.launcherFor { languageVersion = jvm }
}

// For validation, on CI the generated output is uploaded, so the test must produce output in
// DOKKA_TEST_OUTPUT_PATH. For Gradle up-to-date checks the output dir must be specified.
val testOutputPath = System.getenv("DOKKA_TEST_OUTPUT_PATH")
inputs.property("testOutputPath", testOutputPath).optional(true)
if (testOutputPath != null) {
outputs.dir(testOutputPath).withPropertyName("testOutput")
}
}
}
configure()
}
}

//region project tests management

// set up task ordering - template projects (which are generally faster) should be tested before external projects
val testTemplateProjectsTasks = tasks.withType<Test>().matching { it.name.startsWith("testTemplateProject") }
val testExternalProjectsTasks = tasks.withType<Test>().matching { it.name.startsWith("testExternalProject") }

testTemplateProjectsTasks.configureEach {
shouldRunAfter(tasks.test)
}
testExternalProjectsTasks.configureEach {
shouldRunAfter(tasks.test)
shouldRunAfter(testTemplateProjectsTasks)
}

// define lifecycle tasks for project tests
val testAllTemplateProjects by tasks.registering {
description = "Lifecycle task for running all template-project tests"
group = VERIFICATION_GROUP
dependsOn(testTemplateProjectsTasks)
doNotTrackState("lifecycle task, should always run")
}

val testAllExternalProjects by tasks.registering {
description = "Lifecycle task for running all external-project tests"
group = VERIFICATION_GROUP
shouldRunAfter(testAllTemplateProjects)
dependsOn(testExternalProjectsTasks)
doNotTrackState("lifecycle task, should always run")
}

val integrationTest by tasks.registering {
description = "Lifecycle task for running integration tests"
// TODO - Refactor Maven and CLI integration tests to use Test Suites
// - Reimplement dokkabuild.test-integration.gradle.kts so that `integrationTest` is defined once there
dependsOn(tasks.withType<Test>()) // all tests in this project are integration tests
}
//endregion

val checkoutBioJava by tasks.registering(GitCheckoutTask::class) {
uri = "https://github.com/biojava/biojava.git"
Expand Down

0 comments on commit 8b6dba6

Please sign in to comment.