diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2023-11-10 11:46:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-10 11:46:54 +0100 |
commit | 8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch) | |
tree | 1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-integration-tests/gradle/src | |
parent | a44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff) | |
download | dokka-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')
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() +} |