aboutsummaryrefslogtreecommitdiff
path: root/dokka-integration-tests/gradle/src
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-11-10 11:46:54 +0100
committerGitHub <noreply@github.com>2023-11-10 11:46:54 +0100
commit8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch)
tree1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-integration-tests/gradle/src
parenta44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff)
downloaddokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.gz
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.bz2
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.zip
Restructure the project to utilize included builds (#3174)
* Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich <whyoleg@gmail.com>
Diffstat (limited to 'dokka-integration-tests/gradle/src')
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt42
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleCachingIntegrationTest.kt142
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Android0GradleIntegrationTest.kt100
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicCachingIntegrationTest.kt74
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGradleIntegrationTest.kt201
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGroovyIntegrationTest.kt96
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Collector0IntegrationTest.kt83
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/ConfigurationTest.kt76
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/GradleRelocatedCachingIntegrationTest.kt38
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/JsIRGradleIntegrationTest.kt67
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt103
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule1IntegrationTest.kt58
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multiplatform0GradleIntegrationTest.kt57
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/SequentialTasksExecutionStressTest.kt48
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/TestedVersions.kt72
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Versioning0IntegrationTest.kt87
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmGradleIntegrationTest.kt66
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmJsWasiGradleIntegrationTest.kt65
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/CoroutinesGradleIntegrationTest.kt70
-rw-r--r--dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/SerializationGradleIntegrationTest.kt64
-rw-r--r--dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleIntegrationTest.kt84
-rw-r--r--dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/BuildVersions.kt52
-rw-r--r--dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/TestEnvironment.kt18
23 files changed, 1763 insertions, 0 deletions
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt
new file mode 100644
index 00000000..bf0fc808
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/StdLibDocumentationIntegrationTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it
+
+import java.net.URL
+import kotlin.test.Test
+
+class StdLibDocumentationIntegrationTest {
+
+ /**
+ * Documentation for Enum's synthetic values() and valueOf() functions is only present in source code,
+ * but not present in the descriptors. However, Dokka needs to generate documentation for these functions,
+ * so it ships with hardcoded kdoc templates.
+ *
+ * This test exists to make sure documentation for these hardcoded synthetic functions does not change,
+ * and fails if it does, indicating that it needs to be updated.
+ */
+ @Test
+ fun shouldAssertEnumDocumentationHasNotChanged() {
+ val sourcesLink = "https://raw.githubusercontent.com/JetBrains/kotlin/master/core/builtins/native/kotlin/Enum.kt"
+ val sources = URL(sourcesLink).readText()
+
+ val expectedValuesDoc =
+ " /**\n" +
+ " * Returns an array containing the constants of this enum type, in the order they're declared.\n" +
+ " * This method may be used to iterate over the constants.\n" +
+ " * @values\n" +
+ " */"
+ check(sources.contains(expectedValuesDoc))
+
+ val expectedValueOfDoc =
+ " /**\n" +
+ " * Returns the enum constant of this type with the specified name. The string must match exactly " +
+ "an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)\n" +
+ " * @throws IllegalArgumentException if this enum type has no constant with the specified name\n" +
+ " * @valueOf\n" +
+ " */"
+ check(sources.contains(expectedValueOfDoc))
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleCachingIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleCachingIntegrationTest.kt
new file mode 100644
index 00000000..e72d2490
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleCachingIntegrationTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.util.GradleVersion
+import java.io.File
+import kotlin.test.assertTrue
+
+abstract class AbstractGradleCachingIntegrationTest : AbstractGradleIntegrationTest() {
+
+ fun setupProject(buildVersions: BuildVersions, project: File) {
+ val templateProjectDir = File("projects", "it-basic")
+ project.mkdirs()
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(project, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(project, "src"))
+ val customResourcesDir = File(templateProjectDir, "customResources")
+ if(customResourcesDir.exists() && customResourcesDir.isDirectory) {
+ val destination = File(project.parentFile, "customResources")
+ destination.mkdirs()
+ destination.deleteRecursively()
+ customResourcesDir.copyRecursively(destination)
+ }
+
+ // clean local cache for each test
+ if (buildVersions.gradleVersion >= GradleVersion.version("7.0")) {
+ //Gradle 7.0 removed the old syntax
+ project.toPath().resolve("settings.gradle.kts").toFile().appendText(
+ """
+ buildCache {
+ local {
+ // Set local build cache directory.
+ directory = File("${projectDir.invariantSeparatorsPath}", "build-cache")
+ }
+ }
+ """.trimIndent()
+ )
+ } else {
+ project.toPath().resolve("settings.gradle.kts").toFile().appendText(
+ """
+ buildCache {
+ local<DirectoryBuildCache> {
+ // Set local build cache directory.
+ directory = File("${projectDir.invariantSeparatorsPath}", "build-cache")
+ }
+ }
+ """.trimIndent()
+ )
+ }
+ }
+
+ fun File.assertHtmlOutputDir() {
+ assertTrue(isDirectory, "Missing dokka html output directory")
+
+ val imagesDir = File(this, "images")
+ assertTrue(imagesDir.isDirectory, "Missing images directory")
+
+ val scriptsDir = File(this, "scripts")
+ assertTrue(scriptsDir.isDirectory, "Missing scripts directory")
+ val reactFile = File(this, "scripts/main.js")
+ assertTrue(reactFile.isFile, "Missing main.js")
+
+ val stylesDir = File(this, "styles")
+ assertTrue(stylesDir.isDirectory, "Missing styles directory")
+ val reactStyles = File(this, "styles/main.css")
+ assertTrue(reactStyles.isFile, "Missing main.css")
+
+ val navigationHtml = File(this, "navigation.html")
+ assertTrue(navigationHtml.isFile, "Missing navigation.html")
+
+ val moduleOutputDir = File(this, "-basic -project")
+ assertTrue(moduleOutputDir.isDirectory, "Missing module directory")
+
+ val moduleIndexHtml = File(this, "index.html")
+ assertTrue(moduleIndexHtml.isFile, "Missing module index.html")
+
+ val modulePackageDir = File(moduleOutputDir, "it.basic")
+ assertTrue(modulePackageDir.isDirectory, "Missing it.basic package directory")
+
+ val modulePackageIndexHtml = File(modulePackageDir, "index.html")
+ assertTrue(modulePackageIndexHtml.isFile, "Missing module package index.html")
+
+ val moduleJavaPackageDir = File(moduleOutputDir, "it.basic.java")
+ assertTrue(moduleJavaPackageDir.isDirectory, "Missing it.basic.java package directory")
+
+ allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoSuppressedMarker(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+
+ assertTrue(
+ allHtmlFiles().any { file -> "Basic Project" in file.readText() },
+ "Expected configured moduleName to be present in html"
+ )
+
+ assertTrue(
+ allHtmlFiles().any { file ->
+ "https://github.com/Kotlin/dokka/tree/master/" +
+ "dokka-integration-tests/gradle/projects/it-basic/" +
+ "src/main/kotlin/it/basic/PublicClass.kt" in file.readText()
+ },
+ "Expected `PublicClass` source link to GitHub"
+ )
+
+ assertTrue(
+ allHtmlFiles().any { file ->
+ "https://github.com/Kotlin/dokka/tree/master/" +
+ "dokka-integration-tests/gradle/projects/it-basic/" +
+ "src/main/java/it/basic/java/SampleJavaClass.java" in file.readText()
+ },
+ "Expected `SampleJavaClass` source link to GitHub"
+ )
+
+ val anchorsShouldNotHaveHashes = "<a data-name=\".*#.*\"\\sanchor-label=\"*.*\">".toRegex()
+ assertTrue(
+ allHtmlFiles().all { file ->
+ !anchorsShouldNotHaveHashes.containsMatchIn(file.readText())
+ },
+ "Anchors should not have hashes inside"
+ )
+
+ assertTrue(
+ stylesDir.resolve("logo-styles.css").readText().contains(
+ "--dokka-logo-image-url: url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Ubuntu_logo.svg');",
+ )
+ )
+ assertTrue(stylesDir.resolve("custom-style-to-add.css").isFile)
+ assertTrue(stylesDir.resolve("custom-style-to-add.css").readText().contains("/* custom stylesheet */"))
+ allHtmlFiles().forEach { file ->
+ if(file.name != "navigation.html") assertTrue("custom-style-to-add.css" in file.readText(), "custom styles not added to html file ${file.name}")
+ }
+ assertTrue(imagesDir.resolve("custom-resource.svg").isFile)
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Android0GradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Android0GradleIntegrationTest.kt
new file mode 100644
index 00000000..209d6284
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Android0GradleIntegrationTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+internal class AndroidTestedVersionsArgumentsProvider : TestedVersionsArgumentsProvider(TestedVersions.ANDROID)
+
+class Android0GradleIntegrationTest : AbstractGradleIntegrationTest() {
+
+ companion object {
+ /**
+ * Indicating whether or not the current machine executing the test is a CI
+ */
+ private val isCI: Boolean get() = System.getenv("CI") == "true"
+
+ private val isAndroidSdkInstalled: Boolean = System.getenv("ANDROID_SDK_ROOT") != null ||
+ System.getenv("ANDROID_HOME") != null
+
+ fun assumeAndroidSdkInstalled() {
+ if (isCI) return
+ if (!isAndroidSdkInstalled) {
+ throw IllegalStateException("Expected Android SDK to be installed")
+ }
+ }
+ }
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ assumeAndroidSdkInstalled()
+ val templateProjectDir = File("projects", "it-android-0")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .filterNot { it.name == "local.properties" }
+ .filterNot { it.name.startsWith("gradlew") }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AndroidTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(buildVersions, "dokkaHtml", "-i", "-s").buildRelaxed()
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ val htmlOutputDir = File(projectDir, "build/dokka/html")
+ assertTrue(htmlOutputDir.isDirectory, "Missing html output directory")
+
+ assertTrue(
+ htmlOutputDir.allHtmlFiles().count() > 0,
+ "Expected html files in html output directory"
+ )
+
+ htmlOutputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file, knownUnresolvedDRIs)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+
+ assertTrue(
+ htmlOutputDir.allHtmlFiles().any { file ->
+ "https://developer.android.com/reference/kotlin/android/content/Context.html" in file.readText()
+ }, "Expected link to developer.android.com"
+ )
+
+ assertTrue(
+ htmlOutputDir.allHtmlFiles().any { file ->
+ "https://developer.android.com/reference/kotlin/androidx/appcompat/app/AppCompatActivity.html" in
+ file.readText()
+ }, "Expected link to developer.android.com/.../androidx/"
+ )
+
+ htmlOutputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ }
+ }
+
+ // TODO: remove this list when https://github.com/Kotlin/dokka/issues/1306 is closed
+ private val knownUnresolvedDRIs = setOf(
+ "it.android/IntegrationTestActivity/findViewById/#kotlin.Int/PointingToGenericParameters(0)/",
+ "it.android/IntegrationTestActivity/getExtraData/#java.lang.Class[TypeParam(bounds=[androidx.core.app.ComponentActivity.ExtraData])]/PointingToGenericParameters(0)/",
+ "it.android/IntegrationTestActivity/getSystemService/#java.lang.Class[TypeParam(bounds=[kotlin.Any])]/PointingToGenericParameters(0)/",
+ "it.android/IntegrationTestActivity/requireViewById/#kotlin.Int/PointingToGenericParameters(0)/"
+ )
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicCachingIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicCachingIntegrationTest.kt
new file mode 100644
index 00000000..bab55154
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicCachingIntegrationTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class BasicCachingIntegrationTest : AbstractGradleCachingIntegrationTest() {
+
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ setupProject(buildVersions, projectDir)
+
+ runAndAssertOutcomeAndContents(buildVersions, TaskOutcome.SUCCESS)
+ runAndAssertOutcomeAndContents(buildVersions, TaskOutcome.FROM_CACHE)
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun localDirectoryPointingToRoot(buildVersions: BuildVersions) {
+ setupProject(buildVersions, projectDir)
+
+ fun String.findAndReplace(oldValue: String, newValue: String): String {
+ assertTrue(oldValue in this, "Expected to replace '$oldValue'")
+ return replace(oldValue, newValue)
+ }
+ val projectKts = projectDir.resolve("build.gradle.kts")
+
+ projectKts.readText()
+ .findAndReplace("localDirectory.set(file(\"src/main\"))", "localDirectory.set(projectDir)")
+ .findAndReplace("integration-tests/gradle/projects/it-basic/src/main", "integration-tests/gradle/projects/it-basic")
+ .also { projectKts.writeText(it) }
+
+ runAndAssertOutcomeAndContents(buildVersions, TaskOutcome.SUCCESS)
+ projectDir.resolve("unrelated.txt").writeText("modified")
+ // despite projectDir is used as an input in localDirectory, changing its contents shouldn't invalidate the cache
+ runAndAssertOutcomeAndContents(buildVersions, TaskOutcome.FROM_CACHE)
+
+ projectKts.readText()
+ .findAndReplace("localDirectory.set(projectDir)", "localDirectory.set(file(\"src\"))")
+ .also { projectKts.writeText(it) }
+ // changing localDirectory path invalidates cached task results
+ runAndAssertOutcome(buildVersions, TaskOutcome.SUCCESS)
+ }
+
+
+ private fun runAndAssertOutcomeAndContents(buildVersions: BuildVersions, expectedOutcome: TaskOutcome) {
+ runAndAssertOutcome(buildVersions, expectedOutcome)
+ File(projectDir, "build/dokka/html").assertHtmlOutputDir()
+ }
+
+ private fun runAndAssertOutcome(buildVersions: BuildVersions, expectedOutcome: TaskOutcome) {
+ val result = createGradleRunner(
+ buildVersions,
+ "clean",
+ "dokkaHtml",
+ "-i",
+ "-s",
+ "-Dorg.gradle.caching.debug=true",
+ "--build-cache"
+ ).buildRelaxed()
+
+ assertEquals(expectedOutcome, assertNotNull(result.task(":dokkaHtml")).outcome)
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGradleIntegrationTest.kt
new file mode 100644
index 00000000..daf029fc
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGradleIntegrationTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.*
+
+class BasicGradleIntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-basic")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ val customResourcesDir = File(templateProjectDir, "customResources")
+
+ if (customResourcesDir.exists() && customResourcesDir.isDirectory) {
+ val destination = File(projectDir.parentFile, "customResources")
+ destination.mkdirs()
+ destination.deleteRecursively()
+ customResourcesDir.copyRecursively(destination)
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ runAndAssertOutcome(buildVersions, TaskOutcome.SUCCESS)
+ runAndAssertOutcome(buildVersions, TaskOutcome.UP_TO_DATE)
+ }
+
+ private fun runAndAssertOutcome(buildVersions: BuildVersions, expectedOutcome: TaskOutcome) {
+ val result = createGradleRunner(
+ buildVersions,
+ "dokkaHtml",
+ "dokkaJavadoc",
+ "dokkaGfm",
+ "dokkaJekyll",
+ "-i",
+ "-s"
+ ).buildRelaxed()
+
+ assertEquals(expectedOutcome, assertNotNull(result.task(":dokkaHtml")).outcome)
+ assertEquals(expectedOutcome, assertNotNull(result.task(":dokkaJavadoc")).outcome)
+ assertEquals(expectedOutcome, assertNotNull(result.task(":dokkaGfm")).outcome)
+ assertEquals(expectedOutcome, assertNotNull(result.task(":dokkaJekyll")).outcome)
+
+ File(projectDir, "build/dokka/html").assertHtmlOutputDir()
+ File(projectDir, "build/dokka/javadoc").assertJavadocOutputDir()
+ File(projectDir, "build/dokka/gfm").assertGfmOutputDir()
+ File(projectDir, "build/dokka/jekyll").assertJekyllOutputDir()
+ }
+
+ private fun File.assertHtmlOutputDir() {
+ assertTrue(isDirectory, "Missing dokka html output directory")
+
+ val imagesDir = File(this, "images")
+ assertTrue(imagesDir.isDirectory, "Missing images directory")
+
+ val scriptsDir = File(this, "scripts")
+ assertTrue(scriptsDir.isDirectory, "Missing scripts directory")
+ val reactFile = File(this, "scripts/main.js")
+ assertTrue(reactFile.isFile, "Missing main.js")
+
+ val stylesDir = File(this, "styles")
+ assertTrue(stylesDir.isDirectory, "Missing styles directory")
+ val reactStyles = File(this, "styles/main.css")
+ assertTrue(reactStyles.isFile, "Missing main.css")
+
+ val navigationHtml = File(this, "navigation.html")
+ assertTrue(navigationHtml.isFile, "Missing navigation.html")
+
+ val moduleOutputDir = File(this, "-basic -project")
+ assertTrue(moduleOutputDir.isDirectory, "Missing module directory")
+
+ val moduleIndexHtml = File(this, "index.html")
+ assertTrue(moduleIndexHtml.isFile, "Missing module index.html")
+
+ val modulePackageDir = File(moduleOutputDir, "it.basic")
+ assertTrue(modulePackageDir.isDirectory, "Missing it.basic package directory")
+
+ val modulePackageIndexHtml = File(modulePackageDir, "index.html")
+ assertTrue(modulePackageIndexHtml.isFile, "Missing module package index.html")
+
+ val moduleJavaPackageDir = File(moduleOutputDir, "it.basic.java")
+ assertTrue(moduleJavaPackageDir.isDirectory, "Missing it.basic.java package directory")
+
+ allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoSuppressedMarker(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+
+ assertTrue(
+ allHtmlFiles().any { file -> "Basic Project" in file.readText() },
+ "Expected configured moduleName to be present in html"
+ )
+
+ assertTrue(
+ allHtmlFiles().any { file ->
+ "https://github.com/Kotlin/dokka/tree/master/" +
+ "dokka-integration-tests/gradle/projects/it-basic/" +
+ "src/main/kotlin/it/basic/PublicClass.kt" in file.readText()
+ },
+ "Expected `PublicClass` source link to GitHub"
+ )
+
+ assertTrue(
+ allHtmlFiles().any { file ->
+ "https://github.com/Kotlin/dokka/tree/master/" +
+ "dokka-integration-tests/gradle/projects/it-basic/" +
+ "src/main/java/it/basic/java/SampleJavaClass.java" in file.readText()
+ },
+ "Expected `SampleJavaClass` source link to GitHub"
+ )
+
+ val anchorsShouldNotHaveHashes = "<a data-name=\".*#.*\"\\sanchor-label=\"*.*\">".toRegex()
+ assertTrue(
+ allHtmlFiles().all { file ->
+ !anchorsShouldNotHaveHashes.containsMatchIn(file.readText())
+ },
+ "Anchors should not have hashes inside"
+ )
+
+ assertTrue(
+ stylesDir.resolve("logo-styles.css").readText().contains(
+ "--dokka-logo-image-url: url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Ubuntu_logo.svg');",
+ )
+ )
+ assertTrue(stylesDir.resolve("custom-style-to-add.css").isFile)
+ assertTrue(stylesDir.resolve("custom-style-to-add.css").readText().contains("/* custom stylesheet */"))
+ allHtmlFiles().forEach { file ->
+ if (file.name != "navigation.html") assertTrue(
+ "custom-style-to-add.css" in file.readText(),
+ "custom styles not added to html file ${file.name}"
+ )
+ }
+ assertTrue(imagesDir.resolve("custom-resource.svg").isFile)
+
+ assertConfiguredVisibility(this)
+ }
+
+ private fun File.assertJavadocOutputDir() {
+ assertTrue(isDirectory, "Missing dokka javadoc output directory")
+
+ val indexFile = File(this, "index.html")
+ assertTrue(indexFile.isFile, "Missing index.html")
+ assertTrue(
+ """<title>Basic Project 1.9.20-SNAPSHOT API </title>""" in indexFile.readText(),
+ "Header with version number not present in index.html"
+ )
+
+ assertTrue {
+ allHtmlFiles().all {
+ "0.0.1" !in it.readText()
+ }
+ }
+ }
+
+ private fun File.assertGfmOutputDir() {
+ assertTrue(isDirectory, "Missing dokka gfm output directory")
+ }
+
+ private fun File.assertJekyllOutputDir() {
+ assertTrue(isDirectory, "Missing dokka jekyll output directory")
+ }
+
+ private fun assertConfiguredVisibility(outputDir: File) {
+ val allHtmlFiles = outputDir.allHtmlFiles().toList()
+
+ assertContentVisibility(
+ contentFiles = allHtmlFiles,
+ documentPublic = true,
+ documentProtected = true, // sourceSet documentedVisibilities
+ documentInternal = false,
+ documentPrivate = true // for overriddenVisibility package
+ )
+
+ assertContainsFilePaths(
+ outputFiles = allHtmlFiles,
+ expectedFilePaths = listOf(
+ // documentedVisibilities is overridden for package `overriddenVisibility` specifically
+ // to include private code, so html pages for it are expected to have been created
+ Regex("it\\.overriddenVisibility/-visible-private-class/private-method\\.html"),
+ Regex("it\\.overriddenVisibility/-visible-private-class/private-val\\.html"),
+ )
+ )
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGroovyIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGroovyIntegrationTest.kt
new file mode 100644
index 00000000..0d7d32c0
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/BasicGroovyIntegrationTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class BasicGroovyIntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-basic-groovy")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(buildVersions, "dokkaHtml", "dokkaJavadoc", "dokkaGfm", "dokkaJekyll", "-i", "-s")
+ .buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtml")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaJavadoc")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaGfm")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaJekyll")).outcome)
+
+ File(projectDir, "build/dokka/customHtml").assertKdocOutputDir()
+ File(projectDir, "build/dokka/customJavadoc").assertJavadocOutputDir()
+ File(projectDir, "build/dokka/customGfm").assertGfmOutputDir()
+ File(projectDir, "build/dokka/customJekyll").assertJekyllOutputDir()
+ }
+
+ private fun File.assertKdocOutputDir() {
+ assertTrue(isDirectory, "Missing dokka html output directory")
+
+ val imagesDir = File(this, "images")
+ assertTrue(imagesDir.isDirectory, "Missing images directory")
+
+ val scriptsDir = File(this, "scripts")
+ assertTrue(scriptsDir.isDirectory, "Missing scripts directory")
+
+ val stylesDir = File(this, "styles")
+ assertTrue(stylesDir.isDirectory, "Missing styles directory")
+
+ val navigationHtml = File(this, "navigation.html")
+ assertTrue(navigationHtml.isFile, "Missing navigation.html")
+
+ val moduleOutputDir = File(this, "it-basic-groovy")
+ assertTrue(moduleOutputDir.isDirectory, "Missing module directory")
+
+ val moduleIndexHtml = File(this, "index.html")
+ assertTrue(moduleIndexHtml.isFile, "Missing module index.html")
+
+ val modulePackageDir = File(moduleOutputDir, "it.basic")
+ assertTrue(modulePackageDir.isDirectory, "Missing it.basic package directory")
+
+ val modulePackageIndexHtml = File(modulePackageDir, "index.html")
+ assertTrue(modulePackageIndexHtml.isFile, "Missing module package index.html")
+
+ val moduleJavaPackageDir = File(moduleOutputDir, "it.basic.java")
+ assertTrue(moduleJavaPackageDir.isDirectory, "Missing it.basic.java package directory")
+
+ allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+
+ private fun File.assertJavadocOutputDir() {
+ assertTrue(isDirectory, "Missing dokka javadoc output directory")
+ }
+
+ private fun File.assertGfmOutputDir() {
+ assertTrue(isDirectory, "Missing dokka gfm output directory")
+ }
+
+ private fun File.assertJekyllOutputDir() {
+ assertTrue(isDirectory, "Missing dokka jekyll output directory")
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Collector0IntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Collector0IntegrationTest.kt
new file mode 100644
index 00000000..f31cf2bb
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Collector0IntegrationTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class Collector0IntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-collector-0")
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+ File(templateProjectDir, "moduleA").copyRecursively(File(projectDir, "moduleA"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(
+ buildVersions,
+ ":moduleA:dokkaHtmlCollector",
+ ":moduleA:dokkaJavadocCollector",
+ ":moduleA:dokkaGfmCollector",
+ ":moduleA:dokkaJekyllCollector",
+ "-i", "-s"
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaHtmlCollector")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaJavadocCollector")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaGfmCollector")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaJekyllCollector")).outcome)
+
+ File(projectDir, "moduleA/build/dokka/htmlCollector").assertHtmlOutputDir()
+ File(projectDir, "moduleA/build/dokka/javadocCollector").assertJavadocOutputDir()
+ File(projectDir, "moduleA/build/dokka/gfmCollector").assertGfmOutputDir()
+ File(projectDir, "moduleA/build/dokka/jekyllCollector").assertJekyllOutputDir()
+ }
+
+ private fun File.assertHtmlOutputDir() {
+ assertTrue(isDirectory, "Missing dokka htmlCollector output directory")
+ allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+
+ assertTrue(
+ allHtmlFiles().any { file -> "moduleB" in file.readText() },
+ "Expected moduleB to be present in html"
+ )
+
+ assertTrue(
+ allHtmlFiles().any { file -> "moduleC" in file.readText() },
+ "Expected moduleC to be present in html"
+ )
+ }
+
+ private fun File.assertJavadocOutputDir() {
+ assertTrue(isDirectory, "Missing dokka javadocCollector output directory")
+ }
+
+ private fun File.assertJekyllOutputDir() {
+ assertTrue(isDirectory, "Missing dokka jekyllCollector output directory")
+ }
+
+ private fun File.assertGfmOutputDir() {
+ assertTrue(isDirectory, "Missing dokka gfmCollector output directory")
+ }
+}
+
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/ConfigurationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/ConfigurationTest.kt
new file mode 100644
index 00000000..99031542
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/ConfigurationTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+/**
+ * Tests for Dokka's configuration options of the Gradle runner.
+ *
+ * Options can be checked to work in combination with each other:
+ * for instance, you can check that `reportUndocumented` and `failOnWarning`
+ * work in synergy when both set to true.
+ *
+ * Configuration options can be passed as project properties using Gradle CLI arguments.
+ * For example, passing `-Pname=value` to Gradle will create a project-wide property with
+ * key `name` and value `value`, which you can use to set the corresponding option's value
+ * using Dokka's configuration DSL.
+ */
+class ConfigurationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-configuration")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ /**
+ * The test project contains some undocumented declarations, so if both `reportUndocumented`
+ * and `failOnWarning` are enabled - it should fail
+ */
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(LatestTestedVersionsArgumentsProvider::class)
+ @Suppress("FunctionName")
+ fun `should fail with DokkaException and readable message if failOnWarning is triggered`(
+ buildVersions: BuildVersions
+ ) {
+ val result = createGradleRunner(
+ buildVersions,
+ "-info",
+ "-stacktrace",
+ "-Preport_undocumented=true",
+ "-Pfail_on_warning=true",
+ "dokkaHtml"
+ ).buildAndFail()
+
+ assertEquals(TaskOutcome.FAILED, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ result.output.contains("> Task :dokkaHtml FAILED")
+ result.output.contains(
+ """
+ FAILURE: Build failed with an exception\\.
+
+ \* What went wrong:
+ Execution failed for task ':dokkaHtml'\\.
+ > Failed with warningCount=\d and errorCount=\d
+ """.trimIndent().toRegex()
+ )
+
+ result.output.contains(
+ "Caused by: org\\.jetbrains\\.dokka\\.DokkaException: Failed with warningCount=\\d and errorCount=\\d".toRegex()
+ )
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/GradleRelocatedCachingIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/GradleRelocatedCachingIntegrationTest.kt
new file mode 100644
index 00000000..edfdea32
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/GradleRelocatedCachingIntegrationTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class GradleRelocatedCachingIntegrationTest : AbstractGradleCachingIntegrationTest() {
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ setupProject(buildVersions, projectFolder(1))
+ setupProject(buildVersions, projectFolder(2))
+
+ runAndAssertOutcomeAndContents(buildVersions, projectFolder(1), TaskOutcome.SUCCESS)
+ runAndAssertOutcomeAndContents(buildVersions, projectFolder(2), TaskOutcome.FROM_CACHE)
+ }
+
+ private fun runAndAssertOutcomeAndContents(buildVersions: BuildVersions, project: File, expectedOutcome: TaskOutcome) {
+ val result = createGradleRunner(
+ buildVersions,
+ "clean", "dokkaHtml", "-i", "-s", "-Dorg.gradle.caching.debug=true", "--build-cache"
+ ).withProjectDir(project).buildRelaxed()
+
+ assertEquals(expectedOutcome, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ File(project, "build/dokka/html").assertHtmlOutputDir()
+ }
+
+ private fun projectFolder(index: Int) = File(projectDir.absolutePath + index)
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/JsIRGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/JsIRGradleIntegrationTest.kt
new file mode 100644
index 00000000..f097807b
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/JsIRGradleIntegrationTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class JsIRGradleIntegrationTest : AbstractGradleIntegrationTest() {
+
+ private val ignoredKotlinVersions = setOf(
+ // There were some breaking refactoring changes in kotlin react wrapper libs in 1.4.0 -> 1.5.0,
+ // some core react classes were moved from `react-router-dom` to `react` artifacts.
+ // Writing an integration test project that would work for both 1.4.0 and 1.5.0 would involve
+ // ugly solutions, so these versions are ignored. Not a big loss given they are deprecated as of this moment.
+ "1.4.0", "1.4.32"
+ )
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-js-ir-0")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .filterNot { it.name == "local.properties" }
+ .filterNot { it.name.startsWith("gradlew") }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ if (ignoredKotlinVersions.contains(buildVersions.kotlinVersion)) {
+ return
+ }
+
+ val reactVersion = TestedVersions.KT_REACT_WRAPPER_MAPPING[buildVersions.kotlinVersion]
+ ?: throw IllegalStateException("Unspecified version of react for kotlin " + buildVersions.kotlinVersion)
+ val result = createGradleRunner(buildVersions, "-Preact_version=$reactVersion", "dokkaHtml", "-i", "-s").buildRelaxed()
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ val htmlOutputDir = File(projectDir, "build/dokka/html")
+ assertTrue(htmlOutputDir.isDirectory, "Missing html output directory")
+
+ assertTrue(
+ htmlOutputDir.allHtmlFiles().count() > 0,
+ "Expected html files in html output directory"
+ )
+
+ htmlOutputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoUnresolvedLinks(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt
new file mode 100644
index 00000000..ac97ff9f
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.*
+
+class MultiModule0IntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-multimodule-0")
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+ File(templateProjectDir, "moduleA").copyRecursively(File(projectDir, "moduleA"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(
+ buildVersions,
+ ":moduleA:dokkaHtmlMultiModule",
+ ":moduleA:dokkaGfmMultiModule",
+ ":moduleA:dokkaJekyllMultiModule",
+ "-i", "-s"
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaHtmlMultiModule")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaGfmMultiModule")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaJekyllMultiModule")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaHtmlPartial")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaHtmlPartial")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaGfmPartial")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaGfmPartial")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaJekyllPartial")).outcome)
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaJekyllPartial")).outcome)
+
+
+ val outputDir = File(projectDir, "moduleA/build/dokka/htmlMultiModule")
+ assertTrue(outputDir.isDirectory, "Missing dokka output directory")
+
+ assertTrue(
+ outputDir.allHtmlFiles().any(),
+ "Expected at least one html file being generated"
+ )
+
+ outputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ assertNoUnsubstitutedTemplatesInHtml(file)
+ }
+
+ val modulesFile = File(outputDir, "index.html")
+ assertTrue(modulesFile.isFile, "Missing index.html file")
+
+ val modulesFileText = modulesFile.readText()
+ assertTrue(
+ "moduleB" in modulesFileText,
+ "Expected moduleB being mentioned in -modules.html"
+ )
+ assertTrue(
+ "moduleC" in modulesFileText,
+ "Expected moduleC being mentioned in -modules.html"
+ )
+
+ val htmlsWithHomepageLink = outputDir.walkTopDown().filter {
+ it.isFile && it.extension == "html" && it.name != "navigation.html"
+ }.toList()
+
+ assertEquals(16, htmlsWithHomepageLink.size)
+
+ htmlsWithHomepageLink.forEach {
+ assertTrue(
+ it.readText().contains(
+ """https://github.com/Kotlin/dokka/tree/master/dokka-integration-tests/gradle/projects/it-multimodule-0/"""
+ ),
+ "File ${it.absolutePath} doesn't contain link to homepage"
+ )
+ }
+
+ val gfmOutputDir = File(projectDir, "moduleA/build/dokka/gfmMultiModule")
+ assertTrue(gfmOutputDir.isDirectory, "Missing dokka GFM output directory")
+
+ assertTrue(
+ gfmOutputDir.allGfmFiles().any(),
+ "Expected at least one md file being generated"
+ )
+
+ gfmOutputDir.allGfmFiles().forEach { file ->
+ assertFalse("GfmCommand" in file.readText())
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule1IntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule1IntegrationTest.kt
new file mode 100644
index 00000000..59736344
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule1IntegrationTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+/**
+ * This tests mainly checks if linking to relocated methods with no package works
+ */
+class MultiModule1IntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-multimodule-1")
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+ File(templateProjectDir, "first").copyRecursively(File(projectDir, "first"))
+ File(templateProjectDir, "second").copyRecursively(File(projectDir, "second"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(
+ buildVersions,
+ ":second:dokkaHtml",
+ "-i", "-s"
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":second:dokkaHtml")).outcome)
+
+ val outputDir = File(projectDir, "second/build/dokka/html")
+ assertTrue(outputDir.isDirectory, "Missing dokka output directory")
+
+ assertTrue(
+ outputDir.allHtmlFiles().any(),
+ "Expected at least one html file being generated"
+ )
+
+ outputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multiplatform0GradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multiplatform0GradleIntegrationTest.kt
new file mode 100644
index 00000000..d8f5cee2
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multiplatform0GradleIntegrationTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class Multiplatform0GradleIntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-multiplatform-0")
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ // `enableGranularSourceSetsMetadata` and `enableDependencyPropagation` flags are enabled by default since 1.6.20.
+ // remove when this test is executed with Kotlin >= 1.6.20
+ val result = if (buildVersions.kotlinVersion < "1.6.20")
+ createGradleRunner(
+ buildVersions,
+ "dokkaHtml",
+ "-i",
+ "-s",
+ "-Pkotlin.mpp.enableGranularSourceSetsMetadata=true",
+ "-Pkotlin.native.enableDependencyPropagation=false"
+ ).buildRelaxed()
+ else
+ createGradleRunner(buildVersions, "dokkaHtml", "-i", "-s").buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ val dokkaOutputDir = File(projectDir, "build/dokka/html")
+ assertTrue(dokkaOutputDir.isDirectory, "Missing dokka output directory")
+
+ dokkaOutputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/SequentialTasksExecutionStressTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/SequentialTasksExecutionStressTest.kt
new file mode 100644
index 00000000..f331c95c
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/SequentialTasksExecutionStressTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+/**
+ * Creates 100 tasks for the test project and runs them sequentially under low memory settings.
+ *
+ * If the test passes, it's likely there are no noticeable memory leaks.
+ * If it fails, it's likely that memory is leaking somewhere.
+ */
+class SequentialTasksExecutionStressTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-sequential-tasks-execution-stress")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(LatestTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(
+ buildVersions,
+ "runTasks",
+ "--info",
+ "--stacktrace",
+ "-Ptask_number=100",
+ jvmArgs = listOf("-Xmx1G", "-XX:MaxMetaspaceSize=400m")
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":runTasks")).outcome)
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/TestedVersions.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/TestedVersions.kt
new file mode 100644
index 00000000..7c3ea4df
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/TestedVersions.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import java.util.stream.Stream
+
+internal class LatestTestedVersionsArgumentsProvider : TestedVersionsArgumentsProvider(listOf(TestedVersions.LATEST))
+internal open class AllSupportedTestedVersionsArgumentsProvider : TestedVersionsArgumentsProvider(TestedVersions.ALL_SUPPORTED)
+
+internal object TestedVersions {
+
+ val LATEST = BuildVersions("7.6.2", "1.9.20")
+
+ /**
+ * All supported Gradle/Kotlin versions, including [LATEST]
+ *
+ * [Kotlin/Gradle compatibility matrix](https://docs.gradle.org/current/userguide/compatibility.html#kotlin)
+ */
+ val ALL_SUPPORTED =
+ BuildVersions.permutations(
+ gradleVersions = listOf("7.6.2"),
+ kotlinVersions = listOf("1.9.10", "1.8.20", "1.7.20", "1.6.21", "1.5.31"),
+ ) + BuildVersions.permutations(
+ gradleVersions = listOf(*ifExhaustive("7.0", "6.1.1")),
+ kotlinVersions = listOf(*ifExhaustive( "1.8.0", "1.7.0", "1.6.0", "1.5.0"))
+ ) + LATEST
+
+ /**
+ * Supported Android/Gradle/Kotlin versions, including [LATEST]
+ *
+ * Starting with version 7, major Android Gradle Plugin versions are aligned
+ * with major Gradle versions, i.e AGP 7.X will only work with Gradle 7.X
+ *
+ * [AGP/Gradle compatibility matrix](https://developer.android.com/studio/releases/gradle-plugin#updating-gradle)
+ */
+ val ANDROID =
+ BuildVersions.permutations(
+ gradleVersions = listOf("7.4.2", *ifExhaustive("7.0")),
+ kotlinVersions = listOf("1.7.20", "1.6.21", "1.5.31", "1.4.32"),
+ androidGradlePluginVersions = listOf("7.2.0")
+ ) + BuildVersions.permutations(
+ gradleVersions = listOf("6.9", *ifExhaustive("6.1.1", "5.6.4")),
+ kotlinVersions = listOf("1.8.0", "1.7.0", "1.6.0", "1.5.0", "1.4.0"),
+ androidGradlePluginVersions = listOf("4.0.0", *ifExhaustive("3.6.3"))
+ ) + LATEST
+
+ // https://mvnrepository.com/artifact/org.jetbrains.kotlin-wrappers/kotlin-react
+ val KT_REACT_WRAPPER_MAPPING = mapOf(
+ "1.5.0" to "17.0.2-pre.204-kotlin-1.5.0",
+ "1.6.0" to "17.0.2-pre.280-kotlin-1.6.0",
+ "1.5.31" to "17.0.2-pre.265-kotlin-1.5.31",
+ "1.6.21" to "18.0.0-pre.332-kotlin-1.6.21",
+ "1.7.20" to "18.2.0-pre.391",
+ "1.8.0" to "18.2.0-pre.467",
+ "1.8.10" to "18.2.0-pre.490",
+ "1.8.20" to "18.2.0-pre.546",
+ "1.9.0" to "18.2.0-pre.597",
+ "1.9.10" to "18.2.0-pre.597",
+ "1.9.20" to "18.2.0-pre.635",
+ )
+}
+
+abstract class TestedVersionsArgumentsProvider(private val buildVersions: List<BuildVersions>) : ArgumentsProvider {
+ override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+ return buildVersions.stream().map { Arguments.of(it) }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Versioning0IntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Versioning0IntegrationTest.kt
new file mode 100644
index 00000000..381a10e0
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Versioning0IntegrationTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.jsoup.Jsoup
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import kotlin.test.*
+
+class Versioning0IntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-multimodule-versioning-0")
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+ File(templateProjectDir, "first").copyRecursively(File(projectDir, "first"))
+ File(templateProjectDir, "second").copyRecursively(File(projectDir, "second"))
+ }
+
+ /**
+ * This test runs versioning 3 times to simulate how users might use it in the real word
+ *
+ * Each version has a separate task that has a different version number from 1.0 to 1.2 and is placed under `builDir/dokkas/<version>`
+ *
+ * Output is produced in a standard build directory under `build/dokka/htmlMultiModule`
+ */
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(AllSupportedTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(
+ buildVersions,
+ ":dokkaHtmlMultiModuleBaseVersion",
+ "-i", "-s"
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtmlMultiModuleBaseVersion")).outcome)
+ val outputDir = File(projectDir, "dokkas/1.0")
+ assertTrue(outputDir.isDirectory, "Missing dokka output directory")
+
+ val result2 = createGradleRunner(
+ buildVersions,
+ "clean",
+ ":dokkaHtmlMultiModuleNextVersion",
+ "-i", "-s"
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result2.task(":dokkaHtmlMultiModuleNextVersion")).outcome)
+ val outputDir2 = File(projectDir, "dokkas/1.1")
+ assertTrue(outputDir2.isDirectory, "Missing dokka output directory")
+
+ val result3 = createGradleRunner(
+ buildVersions,
+ "clean",
+ ":dokkaHtmlMultiModule",
+ "-i", "-s"
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result3.task(":dokkaHtmlMultiModule")).outcome)
+ val outputDirMultiModule = File(projectDir, "build/dokka/htmlMultiModule")
+ assertTrue(outputDirMultiModule.isDirectory, "Missing dokka output directory")
+
+ val version1_0 = outputDirMultiModule.resolve("older").resolve("1.0")
+ val version1_1 = outputDirMultiModule.resolve("older").resolve("1.1")
+
+ assertTrue(version1_0.isDirectory, "Assumed to have 1.0 version in older dir")
+ assertTrue(version1_1.isDirectory, "Assumed to have 1.1 version in older dir")
+
+ assertFalse(version1_0.resolve("older").exists(), "Subversions should not have older directory")
+ assertFalse(version1_1.resolve("older").exists(), "Subversions should not have older directory")
+
+ val parsedIndex = Jsoup.parse(outputDirMultiModule.resolve("index.html").readText())
+ val dropdown = parsedIndex.select("dokka-template-command").firstOrNull()
+ assertNotNull(dropdown)
+ val links = dropdown.select("a")
+ assertEquals(3, links.count(), "Expected 3 versions to be in dropdown: 1.0, 1.1 and 1.2")
+ assertEquals(
+ listOf("1.2" to "index.html", "1.1" to "older/1.1/index.html", "1.0" to "older/1.0/index.html"),
+ links.map { it.text() to it.attr("href") }
+ )
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmGradleIntegrationTest.kt
new file mode 100644
index 00000000..4280459c
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmGradleIntegrationTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import java.util.stream.Stream
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+internal class WasmTestedVersionsArgumentsProvider : AllSupportedTestedVersionsArgumentsProvider() {
+ override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+ return super.provideArguments(context).filter {
+ val buildVersions = it.get().single() as BuildVersions
+ buildVersions.kotlinVersion >= "1.8.20" && // 1.8.20 is the first public version that can be tested with wasm
+ buildVersions.kotlinVersion <= "1.9.10"// in 1.9.20 wasm target was split into `wasm-js` and `wasm-wasi`
+ }
+ }
+}
+
+class WasmGradleIntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-wasm-basic")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .filterNot { it.name == "local.properties" }
+ .filterNot { it.name.startsWith("gradlew") }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(WasmTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(buildVersions, "dokkaHtml", "-i", "-s").buildRelaxed()
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ val htmlOutputDir = File(projectDir, "build/dokka/html")
+ assertTrue(htmlOutputDir.isDirectory, "Missing html output directory")
+
+ assertTrue(
+ htmlOutputDir.allHtmlFiles().count() > 0,
+ "Expected html files in html output directory"
+ )
+
+ htmlOutputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoUnresolvedLinks(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmJsWasiGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmJsWasiGradleIntegrationTest.kt
new file mode 100644
index 00000000..11580e03
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/WasmJsWasiGradleIntegrationTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import java.util.stream.Stream
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+internal class WasmJsWasiTestedVersionsArgumentsProvider : AllSupportedTestedVersionsArgumentsProvider() {
+ override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+ return super.provideArguments(context).filter {
+ val buildVersions = it.get().single() as BuildVersions
+ buildVersions.kotlinVersion >= "1.9.20" // 1.9.20 is the first public version that can be tested with wasm-js and wasm-wasi
+ }
+ }
+}
+
+class WasmJsWasiGradleIntegrationTest : AbstractGradleIntegrationTest() {
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "it-wasm-js-wasi-basic")
+
+ templateProjectDir.listFiles().orEmpty()
+ .filter { it.isFile }
+ .filterNot { it.name == "local.properties" }
+ .filterNot { it.name.startsWith("gradlew") }
+ .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) }
+
+ File(templateProjectDir, "src").copyRecursively(File(projectDir, "src"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(WasmJsWasiTestedVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(buildVersions, "dokkaHtml", "-i", "-s").buildRelaxed()
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtml")).outcome)
+
+ val htmlOutputDir = File(projectDir, "build/dokka/html")
+ assertTrue(htmlOutputDir.isDirectory, "Missing html output directory")
+
+ assertTrue(
+ htmlOutputDir.allHtmlFiles().count() > 0,
+ "Expected html files in html output directory"
+ )
+
+ htmlOutputDir.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoUnresolvedLinks(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/CoroutinesGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/CoroutinesGradleIntegrationTest.kt
new file mode 100644
index 00000000..6f0d9188
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/CoroutinesGradleIntegrationTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle.kotlin
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.jetbrains.dokka.it.TestOutputCopier
+import org.jetbrains.dokka.it.copyAndApplyGitDiff
+import org.jetbrains.dokka.it.gradle.AbstractGradleIntegrationTest
+import org.jetbrains.dokka.it.gradle.BuildVersions
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import java.util.stream.Stream
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class CoroutinesBuildVersionsArgumentsProvider : ArgumentsProvider {
+ private val buildVersions = BuildVersions.permutations(
+ gradleVersions = listOf("7.4.2"),
+ kotlinVersions = listOf("1.8.10")
+ )
+
+ override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+ return buildVersions.stream().map { Arguments.of(it) }
+ }
+}
+
+class CoroutinesGradleIntegrationTest : AbstractGradleIntegrationTest(), TestOutputCopier {
+
+ override val projectOutputLocation: File by lazy { File(projectDir, "build/dokka/htmlMultiModule") }
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "coroutines/kotlinx-coroutines")
+ templateProjectDir.listFiles().orEmpty()
+ .forEach { topLevelFile -> topLevelFile.copyRecursively(File(projectDir, topLevelFile.name)) }
+
+ copyAndApplyGitDiff(File("projects", "coroutines/coroutines.diff"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(CoroutinesBuildVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(
+ buildVersions,
+ ":dokkaHtmlMultiModule", "-i", "-s",
+ jvmArgs = listOf("-Xmx2G", "-XX:MaxMetaspaceSize=500m")
+ ).buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtmlMultiModule")).outcome)
+
+ assertTrue(projectOutputLocation.isDirectory, "Missing dokka output directory")
+
+ projectOutputLocation.allHtmlFiles().forEach { file ->
+// assertContainsNoErrorClass(file)
+// assertNoUnresolvedLinks(file)
+// assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ assertNoUnsubstitutedTemplatesInHtml(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/SerializationGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/SerializationGradleIntegrationTest.kt
new file mode 100644
index 00000000..ff2849b8
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/kotlin/SerializationGradleIntegrationTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle.kotlin
+
+import org.gradle.testkit.runner.TaskOutcome
+import org.jetbrains.dokka.it.TestOutputCopier
+import org.jetbrains.dokka.it.copyAndApplyGitDiff
+import org.jetbrains.dokka.it.gradle.AbstractGradleIntegrationTest
+import org.jetbrains.dokka.it.gradle.BuildVersions
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import org.junit.jupiter.params.provider.ArgumentsSource
+import java.io.File
+import java.util.stream.Stream
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class SerializationBuildVersionsArgumentsProvider : ArgumentsProvider {
+ private val buildVersions = BuildVersions.permutations(
+ gradleVersions = listOf("7.6.1"),
+ kotlinVersions = listOf("1.9.0")
+ )
+
+ override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+ return buildVersions.stream().map { Arguments.of(it) }
+ }
+}
+
+class SerializationGradleIntegrationTest : AbstractGradleIntegrationTest(), TestOutputCopier {
+
+ override val projectOutputLocation: File by lazy { File(projectDir, "build/dokka/htmlMultiModule") }
+
+ @BeforeTest
+ fun prepareProjectFiles() {
+ val templateProjectDir = File("projects", "serialization/kotlinx-serialization")
+ templateProjectDir.listFiles().orEmpty()
+ .forEach { topLevelFile -> topLevelFile.copyRecursively(File(projectDir, topLevelFile.name)) }
+ copyAndApplyGitDiff(File("projects", "serialization/serialization.diff"))
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(SerializationBuildVersionsArgumentsProvider::class)
+ fun execute(buildVersions: BuildVersions) {
+ val result = createGradleRunner(buildVersions, ":dokkaHtmlMultiModule", "-i", "-s").buildRelaxed()
+
+ assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":dokkaHtmlMultiModule")).outcome)
+
+ assertTrue(projectOutputLocation.isDirectory, "Missing dokka output directory")
+
+ projectOutputLocation.allHtmlFiles().forEach { file ->
+ assertContainsNoErrorClass(file)
+ assertNoUnresolvedLinks(file)
+// assertNoHrefToMissingLocalFileOrDirectory(file)
+ assertNoEmptyLinks(file)
+ assertNoEmptySpans(file)
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleIntegrationTest.kt b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleIntegrationTest.kt
new file mode 100644
index 00000000..5c5d1892
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/AbstractGradleIntegrationTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.internal.DefaultGradleRunner
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.util.GradleVersion
+import org.jetbrains.dokka.it.AbstractIntegrationTest
+import java.io.File
+import java.net.URI
+import kotlin.test.BeforeTest
+
+public abstract class AbstractGradleIntegrationTest : AbstractIntegrationTest() {
+
+ @BeforeTest
+ public fun copyTemplates() {
+ File("projects").listFiles().orEmpty()
+ .filter { it.isFile }
+ .filter { it.name.startsWith("template.") }
+ .forEach { file -> file.copyTo(File(tempFolder, file.name)) }
+ }
+
+ public fun createGradleRunner(
+ buildVersions: BuildVersions,
+ vararg arguments: String,
+ jvmArgs: List<String> = listOf("-Xmx2G", "-XX:MaxMetaspaceSize=1G")
+ ): GradleRunner {
+ return GradleRunner.create()
+ .withProjectDir(projectDir)
+ .forwardOutput()
+ .withJetBrainsCachedGradleVersion(buildVersions.gradleVersion)
+ .withTestKitDir(File("build", "gradle-test-kit").absoluteFile)
+ .withArguments(
+ listOfNotNull(
+ "-Pdokka_it_dokka_version=${System.getenv("DOKKA_VERSION")}",
+ "-Pdokka_it_kotlin_version=${buildVersions.kotlinVersion}",
+ buildVersions.androidGradlePluginVersion?.let { androidVersion ->
+ "-Pdokka_it_android_gradle_plugin_version=$androidVersion"
+ },
+ * arguments
+ )
+ ).run { this as DefaultGradleRunner }
+ .withJvmArguments(jvmArgs)
+ }
+
+ public fun GradleRunner.buildRelaxed(): BuildResult {
+ return try {
+ build()
+ } catch (e: Throwable) {
+ val gradleConnectionException = e.withAllCauses().find { it is GradleConnectionException }
+ if (gradleConnectionException != null) {
+ gradleConnectionException.printStackTrace()
+ throw IllegalStateException("Assumed Gradle connection", gradleConnectionException)
+
+ }
+ throw e
+ }
+ }
+}
+
+private fun GradleRunner.withJetBrainsCachedGradleVersion(version: GradleVersion): GradleRunner {
+ return withGradleDistribution(
+ URI.create(
+ "https://cache-redirector.jetbrains.com/" +
+ "services.gradle.org/distributions/" +
+ "gradle-${version.version}-bin.zip"
+ )
+ )
+}
+
+private fun Throwable.withAllCauses(): Sequence<Throwable> {
+ val root = this
+ return sequence {
+ yield(root)
+ val cause = root.cause
+ if (cause != null && cause != root) {
+ yieldAll(cause.withAllCauses())
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/BuildVersions.kt b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/BuildVersions.kt
new file mode 100644
index 00000000..ab387420
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/BuildVersions.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+import org.gradle.util.GradleVersion
+
+public data class BuildVersions(
+ val gradleVersion: GradleVersion,
+ val kotlinVersion: String,
+ val androidGradlePluginVersion: String? = null,
+) {
+ public constructor(
+ gradleVersion: String,
+ kotlinVersion: String,
+ androidGradlePluginVersion: String? = null
+ ) : this(
+ gradleVersion = GradleVersion.version(gradleVersion),
+ kotlinVersion = kotlinVersion,
+ androidGradlePluginVersion = androidGradlePluginVersion
+ )
+
+ override fun toString(): String {
+ return buildString {
+ append("Gradle ${gradleVersion.version}, Kotlin $kotlinVersion")
+ if (androidGradlePluginVersion != null) {
+ append(", Android $androidGradlePluginVersion")
+ }
+ }
+ }
+
+ public companion object {
+ public fun permutations(
+ gradleVersions: List<String>,
+ kotlinVersions: List<String>,
+ androidGradlePluginVersions: List<String?> = listOf(null)
+ ): List<BuildVersions> {
+ return gradleVersions.distinct().flatMap { gradleVersion ->
+ kotlinVersions.distinct().flatMap { kotlinVersion ->
+ androidGradlePluginVersions.distinct().map { androidVersion ->
+ BuildVersions(
+ gradleVersion = gradleVersion,
+ kotlinVersion = kotlinVersion,
+ androidGradlePluginVersion = androidVersion
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/TestEnvironment.kt b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/TestEnvironment.kt
new file mode 100644
index 00000000..174060aa
--- /dev/null
+++ b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/TestEnvironment.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.it.gradle
+
+public object TestEnvironment {
+ public val isExhaustive: Boolean = checkNotNull(System.getenv("isExhaustive")) {
+ "Missing `isExhaustive` environment variable"
+ }.toBoolean()
+}
+
+/**
+ * Will only return values if [TestEnvironment.isExhaustive] is set to true
+ */
+public inline fun <reified T> ifExhaustive(vararg values: T): Array<out T> {
+ return if (TestEnvironment.isExhaustive) values else emptyArray()
+}