aboutsummaryrefslogtreecommitdiff
path: root/subprojects/analysis-kotlin-api
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-10-27 13:11:41 +0200
committerGitHub <noreply@github.com>2023-10-27 13:11:41 +0200
commitedcd1fb24d01e11b5a8185328255f2005aadf037 (patch)
tree8156df7d2d29d8fd9d0fdaccad0fbb92b26e895f /subprojects/analysis-kotlin-api
parentb1ccc2b346ea858762653933f9dd304b91c18505 (diff)
downloaddokka-edcd1fb24d01e11b5a8185328255f2005aadf037.tar.gz
dokka-edcd1fb24d01e11b5a8185328255f2005aadf037.tar.bz2
dokka-edcd1fb24d01e11b5a8185328255f2005aadf037.zip
Implement analysis test API (#3184)
Diffstat (limited to 'subprojects/analysis-kotlin-api')
-rw-r--r--subprojects/analysis-kotlin-api/build.gradle.kts20
-rw-r--r--subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt49
-rw-r--r--subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt43
-rw-r--r--subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt81
-rw-r--r--subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.kt66
-rw-r--r--subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt55
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt21
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt37
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt97
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.kt67
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt36
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt20
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt223
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt171
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt145
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt177
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt61
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt25
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt54
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt27
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt73
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt56
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt22
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt93
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt69
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt47
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt80
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.kt48
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt27
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt32
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt32
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt44
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt27
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt40
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt26
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt28
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.kt18
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt9
-rw-r--r--subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt41
39 files changed, 2287 insertions, 0 deletions
diff --git a/subprojects/analysis-kotlin-api/build.gradle.kts b/subprojects/analysis-kotlin-api/build.gradle.kts
index 58247479..bf3b5b3c 100644
--- a/subprojects/analysis-kotlin-api/build.gradle.kts
+++ b/subprojects/analysis-kotlin-api/build.gradle.kts
@@ -7,10 +7,30 @@ import org.jetbrains.registerDokkaArtifactPublication
plugins {
id("org.jetbrains.conventions.kotlin-jvm")
id("org.jetbrains.conventions.maven-publish")
+ `java-test-fixtures`
}
dependencies {
compileOnly(projects.core)
+
+ testFixturesApi(projects.core)
+
+ testImplementation(kotlin("test"))
+ testImplementation(projects.subprojects.analysisKotlinDescriptors)
+}
+
+disableTestFixturesPublishing()
+
+/**
+ * Test fixtures are automatically published by default, which at this moment in time is unwanted
+ * as the test api is unstable and is internal to the Dokka project, so it shouldn't be used outside of it.
+ *
+ * @see https://docs.gradle.org/current/userguide/java_testing.html#ex-disable-publishing-of-test-fixtures-variants
+ */
+fun disableTestFixturesPublishing() {
+ val javaComponent = components["java"] as AdhocComponentWithVariants
+ javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() }
+ javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() }
}
registerDokkaArtifactPublication("analysisKotlinApi") {
diff --git a/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt
new file mode 100644
index 00000000..f6632f60
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/SampleJavaAnalysisTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.jvm.java
+
+import org.jetbrains.dokka.analysis.test.api.javaTestProject
+import org.jetbrains.dokka.analysis.test.api.parse
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SampleJavaAnalysisTest {
+
+ /**
+ * Used as a sample for [javaTestProject]
+ */
+ @Test
+ fun sample() {
+ val testProject = javaTestProject {
+ dokkaConfiguration {
+ moduleName = "java-module-name-for-unit-test"
+
+ javaSourceSet {
+ // source-set specific configuration
+ }
+ }
+ javaFile(pathFromSrc = "org/jetbrains/dokka/test/java/Bar.java") {
+ +"""
+ public class Bar {
+ public static void bar() {
+ System.out.println("Bar");
+ }
+ }
+ """
+ }
+ }
+
+ val module = testProject.parse()
+ assertEquals("java-module-name-for-unit-test", module.name)
+ assertEquals(1, module.packages.size)
+
+ val pckg = module.packages[0]
+ assertEquals("org.jetbrains.dokka.test.java", pckg.name)
+ assertEquals(1, pckg.classlikes.size)
+
+ val fooClass = pckg.classlikes[0]
+ assertEquals("Bar", fooClass.name)
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt
new file mode 100644
index 00000000..6c73af1f
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/kotlin/SampleKotlinJvmAnalysisTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.jvm.kotlin
+
+import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.parse
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SampleKotlinJvmAnalysisTest {
+
+ /**
+ * Used as a sample for [kotlinJvmTestProject]
+ */
+ @Test
+ fun sample() {
+ val testProject = kotlinJvmTestProject {
+ dokkaConfiguration {
+ moduleName = "kotlin-jvm-module-name-for-unit-test"
+
+ kotlinSourceSet {
+ // source-set specific configuration
+ }
+ }
+ ktFile(pathFromSrc = "org/jetbrains/dokka/test/kotlin/MyFile.kt") {
+ +"public class Foo {}"
+ }
+ }
+
+ val module = testProject.parse()
+ assertEquals("kotlin-jvm-module-name-for-unit-test", module.name)
+ assertEquals(1, module.packages.size)
+
+ val pckg = module.packages[0]
+ assertEquals("org.jetbrains.dokka.test.kotlin", pckg.name)
+ assertEquals(1, pckg.classlikes.size)
+
+ val fooClass = pckg.classlikes[0]
+ assertEquals("Foo", fooClass.name)
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt
new file mode 100644
index 00000000..fec2ceb8
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/mixed/SampleMixedJvmAnalysisTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.jvm.mixed
+
+import org.jetbrains.dokka.analysis.test.api.mixedJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.parse
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SampleMixedJvmAnalysisTest {
+
+ /**
+ * Used as a sample for [mixedJvmTestProject]
+ */
+ @Test
+ fun sample() {
+ val testProject = mixedJvmTestProject {
+ dokkaConfiguration {
+ moduleName = "mixed-project-module-name-for-unit-test"
+
+ jvmSourceSet {
+ // source-set specific configuration
+ }
+ }
+
+ kotlinSourceDirectory {
+ ktFile(pathFromSrc = "test/MyFile.kt") {
+ +"fun foo(): String = \"Foo\""
+ }
+ javaFile(pathFromSrc = "test/MyJavaFileInKotlin.java") {
+ +"""
+ public class MyJavaFileInKotlin {
+ public static void bar() {
+ System.out.println("Bar");
+ }
+ }
+ """
+ }
+ }
+
+ javaSourceDirectory {
+ ktFile(pathFromSrc = "test/MyFile.kt") {
+ +"fun bar(): String = \"Bar\""
+ }
+ javaFile(pathFromSrc = "test/MyJavaFileInJava.java") {
+ +"""
+ public class MyJavaFileInJava {
+ public static void bar() {
+ System.out.println("Bar");
+ }
+ }
+ """
+ }
+ }
+ }
+
+ val module = testProject.parse()
+ assertEquals("mixed-project-module-name-for-unit-test", module.name)
+ assertEquals(1, module.packages.size)
+
+ val pckg = module.packages[0]
+ assertEquals("test", pckg.name)
+
+ assertEquals(2, pckg.classlikes.size)
+ assertEquals(2, pckg.functions.size)
+
+ val firstClasslike = pckg.classlikes[0]
+ assertEquals("MyJavaFileInKotlin", firstClasslike.name)
+
+ val secondClasslike = pckg.classlikes[1]
+ assertEquals("MyJavaFileInJava", secondClasslike.name)
+
+ val firstFunction = pckg.functions[0]
+ assertEquals("bar", firstFunction.name)
+
+ val secondFunction = pckg.functions[1]
+ assertEquals("foo", secondFunction.name)
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.kt b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.kt
new file mode 100644
index 00000000..55507023
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/moduledocs/PackageDocumentationAnalysisTest.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.analysis.test.moduledocs
+
+import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.useServices
+import org.jetbrains.dokka.model.doc.CodeInline
+import org.jetbrains.dokka.model.doc.Description
+import org.jetbrains.dokka.model.doc.P
+import org.jetbrains.dokka.model.doc.Text
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class PackageDocumentationAnalysisTest {
+
+ @Test
+ fun `should parse include description for a nested package in kotlin-jvm`() {
+ val testProject = kotlinJvmTestProject {
+ dokkaConfiguration {
+ kotlinSourceSet {
+ includes = setOf("/documentation/cool-package-description.md")
+ }
+ }
+
+ ktFile(pathFromSrc = "org/jetbrains/dokka/pckg/docs/test/TestFile.kt") {
+ +"class TestFile {}"
+ }
+
+ mdFile(pathFromProjectRoot = "/documentation/cool-package-description.md") {
+ +"""
+ # Package org.jetbrains.dokka.pckg.docs.test
+
+ This is my test description for the package `org.jetbrains.dokka.pckg.docs.test`,
+ which contains only one file named TestFile.kt. It has one empty class.
+ """
+ }
+ }
+
+ testProject.useServices { context ->
+ val pckg = context.module.packages.single { it.name == "org.jetbrains.dokka.pckg.docs.test" }
+
+ val allPackageDocs = moduleAndPackageDocumentationReader.read(pckg)
+ assertEquals(1, allPackageDocs.size)
+
+ val sourceSetPackageDocs = allPackageDocs.entries.single().value
+ assertEquals(1, sourceSetPackageDocs.children.size)
+
+ val descriptionTag = sourceSetPackageDocs.children.single() as Description
+ assertEquals(1, descriptionTag.children.size)
+
+ val paragraphTag = descriptionTag.children.single() as P
+ assertEquals(3, paragraphTag.children.size)
+
+ val expectedParagraphChildren = listOf(
+ Text("This is my test description for the package "),
+ CodeInline(children = listOf(Text(
+ "org.jetbrains.dokka.pckg.docs.test"
+ ))),
+ Text(", which contains only one file named TestFile.kt. It has one empty class.")
+ )
+ assertEquals(expectedParagraphChildren, paragraphTag.children)
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt
new file mode 100644
index 00000000..618e28a8
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/sample/SampleAnalysisTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.sample
+
+import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.useServices
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class SampleAnalysisTest {
+
+ @Test
+ fun `should return sources of a kotlin sample`() {
+ val testProject = kotlinJvmTestProject {
+ dokkaConfiguration {
+ kotlinSourceSet {
+ additionalSourceRoots = setOf("/samples")
+ }
+ }
+ sampleFile("/samples/stringListOf-sample.kt", fqPackageName = "org.jetbrains.dokka.sample.generator") {
+ +"""
+ import org.jetbrains.dokka.DokkaConfiguration
+ import org.jetbrains.dokka.DokkaGenerator
+ import org.jetbrains.dokka.utilities.DokkaLogger
+
+ fun runGenerator(configuration: DokkaConfiguration, logger: DokkaLogger) {
+ DokkaGenerator(configuration, logger).generate()
+ }
+ """
+ }
+ }
+
+ testProject.useServices { context ->
+ val sampleSourceSet = context.configuration.sourceSets.single()
+
+ val sampleProvider = sampleProviderFactory.build()
+ val sample = sampleProvider.getSample(sampleSourceSet, "org.jetbrains.dokka.sample.generator.runGenerator")
+ assertNotNull(sample)
+
+ val expectedImports = listOf(
+ "import org.jetbrains.dokka.DokkaConfiguration",
+ "import org.jetbrains.dokka.DokkaGenerator",
+ "import org.jetbrains.dokka.utilities.DokkaLogger"
+ ).joinToString(separator = "\n")
+
+ val expectedBody = "DokkaGenerator(configuration, logger).generate()"
+
+ assertEquals(expectedImports, sample.imports)
+ assertEquals(expectedBody, sample.body)
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt
new file mode 100644
index 00000000..64bfd7a3
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestData.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api
+
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * Represents some sort of data of a [TestProject], which normally consists of a number of [TestDataFile].
+ *
+ * This can be anything that can usually be found in a user-defined project:
+ * programming language source code, markdown files with documentation, samples, etc.
+ *
+ * This virtual test data will be materialized and created physically before running Dokka,
+ * and then passed as input files into it.
+ */
+@AnalysisTestDslMarker
+interface TestData {
+ fun getFiles(): List<TestDataFile>
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt
new file mode 100644
index 00000000..5b2233ba
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestDataFile.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api
+
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * Represents a single file of a project's [TestData].
+ *
+ * This file will be materialized and created physically before running Dokka,
+ * and then passed as one of the input files into it.
+ *
+ * @property pathFromProjectRoot this file's path from the root of the project. Must begin
+ * with `/` to not confuse it with relative paths.
+ */
+@AnalysisTestDslMarker
+abstract class TestDataFile(val pathFromProjectRoot: String) {
+
+ init {
+ require(pathFromProjectRoot.startsWith("/")) {
+ "File path going from the project's root must begin with \"/\" to not confuse it with relative paths."
+ }
+ }
+
+ /**
+ * Returns the string contents of this file.
+ *
+ * The contents must be complete, as if the user themselves wrote it. For Kotlin files,
+ * it should return Kotlin source code (including the package and all import statements).
+ * For `.md` files, it should return valid Markdown documentation.
+ *
+ * These contents will be used to populate the real input file to be used by Dokka.
+ */
+ abstract fun getContents(): String
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt
new file mode 100644
index 00000000..9c0fa936
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProject.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.analysis.TestAnalysisContext
+import org.jetbrains.dokka.analysis.test.api.analysis.TestAnalysisServices
+import org.jetbrains.dokka.analysis.test.api.analysis.TestProjectAnalyzer
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.util.withTempDirectory
+import org.jetbrains.dokka.model.DModule
+
+/**
+ * Represents a virtual test project (as if it's user-defined) that will be used to run Dokka.
+ *
+ * A test project consists of some Dokka configuration (represented as [TestDokkaConfiguration])
+ * and some project-specific data like source code and markdown files (represented as [TestData]).
+ *
+ * See [kotlinJvmTestProject], [javaTestProject] and [mixedJvmTestProject] for convenient ways
+ * of bootstrapping test projects.
+ *
+ * See [parse] and [useServices] functions to learn how to run Dokka with this project as input.
+ */
+interface TestProject {
+
+ /**
+ * Verifies that this project is valid from the user's and Dokka's perspectives.
+ * Exists to save time with debugging difficult to catch mistakes, such as copy-pasted
+ * test data that is not applicable to this project.
+ *
+ * Must throw an exception if there's misconfiguration, incorrect / corrupted test data
+ * or API misuse.
+ *
+ * Verification is performed before running Dokka on this project.
+ */
+ fun verify()
+
+ /**
+ * Returns the configuration of this project, which will then be mapped to [DokkaConfiguration].
+ *
+ * This is typically constructed using [BaseTestDokkaConfigurationBuilder].
+ */
+ fun getConfiguration(): TestDokkaConfiguration
+
+ /**
+ * Returns this project's test data - a collection of source code files, markdown files
+ * and whatever else that can be usually found in a user-defined project.
+ */
+ fun getTestData(): TestData
+}
+
+/**
+ * Runs Dokka on the given [TestProject] and returns the generated documentable model.
+ *
+ * Can be used to verify the resulting documentable model, to check that
+ * everything was parsed and converted correctly.
+ *
+ * Usage example:
+ * ```kotlin
+ * val testProject = kotlinJvmTestProject {
+ * ...
+ * }
+ *
+ * val module: DModule = testProject.parse()
+ * ```
+ */
+fun TestProject.parse(): DModule = TestProjectAnalyzer.parse(this)
+
+/**
+ * Runs Dokka on the given [TestProject] and provides not only the resulting documentable model,
+ * but analysis context and configuration as well, which gives you the ability to call public
+ * analysis services.
+ *
+ * Usage example:
+ *
+ * ```kotlin
+ * val testProject = kotlinJvmTestProject {
+ * ...
+ * }
+ *
+ * testProject.useServices { context ->
+ * val pckg: DPackage = context.module.packages.single()
+ *
+ * // use `moduleAndPackageDocumentationReader` service to get documentation of a package
+ * val allPackageDocs: SourceSetDependent<DocumentationNode> = moduleAndPackageDocumentationReader.read(pckg)
+ * }
+ * ```
+ */
+fun TestProject.useServices(block: TestAnalysisServices.(context: TestAnalysisContext) -> Unit) {
+ withTempDirectory { tempDirectory ->
+ val (services, context) = TestProjectAnalyzer.analyze(this, tempDirectory)
+ services.block(context)
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.kt
new file mode 100644
index 00000000..81a20243
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/TestProjectFactory.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.analysis.test.api
+
+import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject
+import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.jvm.mixed.MixedJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * Creates a single-target Kotlin/JVM test project that only has Kotlin source code.
+ *
+ * See [javaTestProject] and [mixedJvmTestProject] if you want to check interoperability
+ * with other JVM languages.
+ *
+ * By default, the sources are put in `/src/main/kotlin`, and the JVM version of Kotlin's
+ * standard library is available on classpath.
+ *
+ * See [parse] and [useServices] functions to learn how to run Dokka with this project as input.
+ *
+ * @sample org.jetbrains.dokka.analysis.test.jvm.kotlin.SampleKotlinJvmAnalysisTest.sample
+ */
+fun kotlinJvmTestProject(init: (@AnalysisTestDslMarker KotlinJvmTestProject).() -> Unit): TestProject {
+ val testData = KotlinJvmTestProject()
+ testData.init()
+ return testData
+}
+
+/**
+ * Creates a Java-only test project.
+ *
+ * This can be used to test Dokka's Java support or specific
+ * corner cases related to parsing Java sources.
+ *
+ * By default, the sources are put in `/src/main/java`. No Kotlin source code is allowed.
+ *
+ * See [parse] and [useServices] functions to learn how to run Dokka with this project as input.
+ *
+ * @sample org.jetbrains.dokka.analysis.test.jvm.java.SampleJavaAnalysisTest.sample
+ */
+fun javaTestProject(init: (@AnalysisTestDslMarker JavaTestProject).() -> Unit): TestProject {
+ val testData = JavaTestProject()
+ testData.init()
+ return testData
+}
+
+/**
+ * Creates a project where a number of JVM language sources are allowed,
+ * like Java and Kotlin sources co-existing in the same source directory.
+ *
+ * This can be used to test interoperability between JVM languages.
+ *
+ * By default, this project consists of a single "jvm" source set, which has two source root directories:
+ * * `/src/main/kotlin`
+ * * `/src/main/java`
+ *
+ * See [parse] and [useServices] functions to learn how to run Dokka with this project as input.
+ *
+ * @sample org.jetbrains.dokka.analysis.test.jvm.mixed.SampleMixedJvmAnalysisTest.sample
+ */
+fun mixedJvmTestProject(init: (@AnalysisTestDslMarker MixedJvmTestProject).() -> Unit): TestProject {
+ val testProject = MixedJvmTestProject()
+ testProject.init()
+ return testProject
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt
new file mode 100644
index 00000000..de6efb1b
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisContext.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.analysis
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.TestProject
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.plugability.DokkaContext
+
+/**
+ * Context and data gathered during the analysis of a [TestProject].
+ */
+class TestAnalysisContext(
+
+ /**
+ * The actual [DokkaContext] that was used to run Dokka.
+ *
+ * Includes all plugins and classes available on classpath during the analysis.
+ */
+ val context: DokkaContext,
+
+ /**
+ * The actual [DokkaConfiguration] that was used to run Dokka.
+ *
+ * It was initially mapped from [TestDokkaConfiguration], and then added to by Dokka itself.
+ */
+ val configuration: DokkaConfiguration,
+
+ /**
+ * The entry point to the documentable model of the analyzed [TestProject].
+ */
+ val module: DModule
+)
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt
new file mode 100644
index 00000000..ab70bbd4
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.analysis
+
+import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.internal.ModuleAndPackageDocumentationReader
+import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
+
+/**
+ * Services exposed in [KotlinAnalysisPlugin] that are ready to be used.
+ *
+ * This class exists purely for convenience and to reduce boilerplate in tests.
+ * It is analogous to calling `context.plugin<KotlinAnalysisPlugin>().querySingle { serviceName }`.
+ */
+class TestAnalysisServices(
+ val sampleProviderFactory: SampleProviderFactory,
+ val moduleAndPackageDocumentationReader: ModuleAndPackageDocumentationReader
+)
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt
new file mode 100644
index 00000000..1668b53f
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.analysis
+
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+import org.jetbrains.dokka.analysis.test.api.TestProject
+import org.jetbrains.dokka.analysis.test.api.configuration.toDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.parse
+import org.jetbrains.dokka.analysis.test.api.useServices
+import org.jetbrains.dokka.analysis.test.api.util.withTempDirectory
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.transformers.documentation.DefaultDocumentableMerger
+import org.jetbrains.dokka.transformers.documentation.DocumentableMerger
+import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import java.io.File
+
+/**
+ * The main logger used for running Dokka and analyzing projects.
+ *
+ * Changing the level to [LoggingLevel.DEBUG] can help with debugging faulty tests
+ * or tricky corner cases.
+ */
+val analysisLogger = DokkaConsoleLogger(minLevel = LoggingLevel.INFO)
+
+/**
+ * Analyzer of the test projects, it is essentially a very simple Dokka runner.
+ *
+ * Takes all virtual files of the given [TestProject], creates the real files for
+ * them in a temporary directory, and then runs Dokka with this temporary directory
+ * as the input user project. This allows us to simulate Dokka's behavior and results
+ * on a made-up project as if it were real and run via the CLI runner.
+ *
+ * Executes only a limited number of steps and uses a small subset of [CoreExtensions]
+ * that are necessary to test the analysis logic.
+ *
+ * Works only with single-module projects, where the source code of this project
+ * resides in the root `src` directory. Works with multiple source sets and targets,
+ * so both simple Kotlin/JVM and more complicated Kotlin Multiplatform project must work.
+ */
+object TestProjectAnalyzer {
+
+ /**
+ * A quick way to analyze a [TestProject], for cases when only the documentable
+ * model is needed to verify the result.
+ *
+ * Creates the input test files, runs Dokka and then deletes them right after the documentable
+ * model has been created, leaving no trailing files or any other garbage behind.
+ *
+ * @see [TestProject.parse] for a user-friendly way to call it
+ */
+ fun parse(testProject: TestProject): DModule {
+ // since we only need documentables, we can delete the input test files right away
+ return withTempDirectory(analysisLogger) { tempDirectory ->
+ val (_, context) = testProject.initialize(outputDirectory = tempDirectory)
+ generateDocumentableModel(context)
+ }
+ }
+
+ /**
+ * Works in the same way as [parse], but it returns the context and configuration used for
+ * running Dokka, and does not delete the input test files at the end of the execution - it
+ * must be taken care of on call site.
+ *
+ * @param persistentDirectory a directory that will be used to generate the input test files into.
+ * It must be available during the test run, especially if services are used,
+ * otherwise parts of Dokka might not work as expected. Can be safely deleted
+ * at the end of the test after all asserts have been run.
+ *
+ * @see [TestProject.useServices] for a user-friendly way to call it
+ */
+ fun analyze(
+ testProject: TestProject,
+ persistentDirectory: File
+ ): Pair<TestAnalysisServices, TestAnalysisContext> {
+ val (dokkaConfiguration, dokkaContext) = testProject.initialize(outputDirectory = persistentDirectory)
+ val analysisServices = createTestAnalysisServices(dokkaContext)
+ val testAnalysisContext = TestAnalysisContext(
+ context = dokkaContext,
+ configuration = dokkaConfiguration,
+ module = generateDocumentableModel(dokkaContext)
+ )
+ return analysisServices to testAnalysisContext
+ }
+
+ /**
+ * Prepares this [TestProject] for analysis by creating
+ * the test files, setting up context and configuration.
+ */
+ private fun TestProject.initialize(outputDirectory: File): Pair<DokkaConfiguration, DokkaContext> {
+ analysisLogger.progress("Initializing and verifying project $this")
+ this.verify()
+ require(outputDirectory.isDirectory) {
+ "outputDirectory has to exist and be a directory: $outputDirectory"
+ }
+ this.initializeTestFiles(relativeToDir = outputDirectory)
+
+ analysisLogger.progress("Creating configuration and context")
+ val testDokkaConfiguration = this.getConfiguration()
+ val dokkaConfiguration = testDokkaConfiguration.toDokkaConfiguration(projectDir = outputDirectory).also {
+ it.verify()
+ }
+ return dokkaConfiguration to createContext(dokkaConfiguration)
+ }
+
+ /**
+ * Takes the virtual [TestDataFile] of this [TestProject] and creates
+ * the real files relative to the [relativeToDir] param.
+ */
+ private fun TestProject.initializeTestFiles(relativeToDir: File) {
+ analysisLogger.progress("Initializing test files relative to the \"$relativeToDir\" directory")
+
+ this.getTestData().getFiles().forEach {
+ val testDataFile = relativeToDir.resolve(it.pathFromProjectRoot.removePrefix("/"))
+ try {
+ testDataFile.parentFile.mkdirs()
+ } catch (e: Exception) {
+ // the IOException thrown from `mkdirs()` has no details and thus is more difficult to debug.
+ throw IllegalStateException("Unable to create dirs \"${testDataFile.parentFile}\"", e)
+ }
+
+ analysisLogger.debug("Creating \"${testDataFile.absolutePath}\"")
+ check(testDataFile.createNewFile()) {
+ "Unable to create a test file: ${testDataFile.absolutePath}"
+ }
+ testDataFile.writeText(it.getContents(), Charsets.UTF_8)
+ }
+ }
+
+ /**
+ * Verifies this [DokkaConfiguration] to make sure there are no unexpected
+ * parameter option values, such as non-existing classpath entries.
+ *
+ * If this method fails, it's likely there's a configuration error in the test,
+ * or an exception must be made in one of the checks.
+ */
+ private fun DokkaConfiguration.verify() {
+ this.includes.forEach { verifyFileExists(it) }
+ this.sourceSets.forEach { sourceSet ->
+ sourceSet.classpath.forEach { verifyFileExists(it) }
+ sourceSet.includes.forEach { verifyFileExists(it) }
+ sourceSet.samples.forEach { verifyFileExists(it) }
+ // we do not verify sourceRoots since the source directory
+ // is not guaranteed to exist even if it was configured.
+ }
+ }
+
+ private fun verifyFileExists(file: File) {
+ if (!file.exists() && !file.absolutePath.contains("non-existing")) {
+ throw IllegalArgumentException(
+ "The provided file does not exist. Bad test data or configuration? " +
+ "If it is done intentionally, add \"non-existing\" to the path or the name. File: \"$file\""
+ )
+ }
+ }
+
+ private fun createContext(dokkaConfiguration: DokkaConfiguration): DokkaContext {
+ analysisLogger.progress("Creating DokkaContext from test configuration")
+ return DokkaContext.create(
+ configuration = dokkaConfiguration,
+ logger = analysisLogger,
+ pluginOverrides = listOf()
+ )
+ }
+
+ /**
+ * Generates the documentable model by using all available [SourceToDocumentableTranslator] extensions,
+ * and then merging all the results into a single [DModule] by calling [DocumentableMerger].
+ */
+ private fun generateDocumentableModel(context: DokkaContext): DModule {
+ analysisLogger.progress("Generating the documentable model")
+ val sourceSetModules = context
+ .configuration
+ .sourceSets
+ .map { sourceSet -> translateSources(sourceSet, context) }
+ .flatten()
+
+ if (sourceSetModules.isEmpty()) {
+ throw IllegalStateException("Got no modules after translating sources. Is the test data set up?")
+ }
+
+ return DefaultDocumentableMerger(context).invoke(sourceSetModules)
+ ?: error("Unable to merge documentables for some reason")
+ }
+
+ /**
+ * Translates input source files to the documentable model by using
+ * all registered [SourceToDocumentableTranslator] core extensions.
+ */
+ private fun translateSources(sourceSet: DokkaConfiguration.DokkaSourceSet, context: DokkaContext): List<DModule> {
+ val translators = context[CoreExtensions.sourceToDocumentableTranslator]
+ require(translators.isNotEmpty()) {
+ "Need at least one source to documentable translator to run tests, otherwise no data will be generated."
+ }
+ analysisLogger.debug("Translating sources for ${sourceSet.sourceSetID}")
+ return translators.map { it.invoke(sourceSet, context) }
+ }
+
+ /**
+ * A helper function to query analysis services, to avoid
+ * boilerplate and misconfiguration in the tests.
+ *
+ * The idea is to provide the users with ready-to-use services,
+ * without them having to know how to query or configure them.
+ */
+ private fun createTestAnalysisServices(context: DokkaContext): TestAnalysisServices {
+ analysisLogger.progress("Creating analysis services")
+ val internalPlugin = context.plugin<InternalKotlinAnalysisPlugin>()
+ return TestAnalysisServices(
+ sampleProviderFactory = internalPlugin.querySingle { sampleProviderFactory },
+ moduleAndPackageDocumentationReader = internalPlugin.querySingle { moduleAndPackageDocumentationReader }
+ )
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt
new file mode 100644
index 00000000..5c5a0daf
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfiguration.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.configuration
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.test.api.TestProject
+
+/**
+ * Configuration options for [TestProject].
+ *
+ * Represents a trimmed-down version of [DokkaConfiguration] that only
+ * exposes properties that can be used by Dokka's analysis implementations.
+ */
+data class TestDokkaConfiguration(
+
+ /**
+ * Name of this [TestProject].
+ *
+ * @see DokkaConfiguration.moduleName
+ */
+ val moduleName: String,
+
+ /**
+ * References Markdown files that contain documentation for this module and packages.
+ *
+ * Contains paths relative to the root of [TestProject], so it must begin with `/`.
+ *
+ * Example: `/docs/module.md`
+ *
+ * @see DokkaConfiguration.includes
+ * @see https://kotlinlang.org/docs/dokka-module-and-package-docs.html
+ */
+ val includes: Set<String> = emptySet(),
+
+ /**
+ * A number of source directories and their configuration options that
+ * make up this project.
+ *
+ * A multiplatform Kotlin project will typically have multiple source sets
+ * for the supported platforms, whereas a single Kotlin/JVM project will have only one.
+ *
+ * @see TestDokkaSourceSet
+ */
+ val sourceSets: Set<TestDokkaSourceSet>
+
+
+) {
+ override fun toString(): String {
+ return "TestDokkaConfiguration(moduleName='$moduleName', includes=$includes, sourceSets=$sourceSets)"
+ }
+}
+
+/**
+ * Configuration options for a collection of source files for a specific platform.
+ *
+ * Represents a trimmed-down version of [DokkaConfiguration.DokkaSourceSet] that only
+ * exposes properties that can be used by Dokka's analysis implementations.
+ *
+ * @see https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets
+ */
+data class TestDokkaSourceSet (
+
+ /**
+ * @see DokkaConfiguration.DokkaSourceSet.analysisPlatform
+ */
+ val analysisPlatform: Platform,
+
+ /**
+ * A unique identifier of this source set in the scope of the project.
+ *
+ * It must be unique even if two source sets have the same name,
+ * the same platform or the same configuration.
+ *
+ * @see DokkaConfiguration.DokkaSourceSet.sourceSetID
+ */
+ val sourceSetID: DokkaSourceSetID,
+
+ /**
+ * A set of source set ids that this source set depends on.
+ *
+ * @see DokkaConfiguration.DokkaSourceSet.dependentSourceSets
+ */
+ val dependentSourceSets: Set<DokkaSourceSetID>,
+
+ /**
+ * A set of directories that contain the source files for this source set.
+ *
+ * A source set typically has only one source root directory, but there can be additional
+ * ones if the project has generated sources that reside separately, or if the `@sample`
+ * tag is used, and the samples reside in a different directory.
+ *
+ * Contains paths relative to the root of [TestProject], so it must begin with `/`.
+ *
+ * @see DokkaConfiguration.DokkaSourceSet.sourceRoots
+ */
+ val sourceRoots: Set<String>,
+
+ /**
+ * A set of JARs to be used for analyzing sources.
+ *
+ * If this [TestProject] exposes types from external libraries, Dokka needs to know
+ * about these libraries so that it can generate correct signatures and documentation.
+ *
+ * Contains absolute paths, can be any file, even if it has nothing to do with unit
+ * tests or test project.
+ *
+ * @see DokkaConfiguration.DokkaSourceSet.classpath
+ */
+ val classpath: Set<String>,
+
+ /**
+ * References Markdown files that contain documentation for this module and packages.
+ *
+ * Contains paths relative to the root of [TestProject], so it must begin with `/`.
+ *
+ * Example: `/docs/module.md`
+ *
+ * @see DokkaConfiguration.DokkaSourceSet.includes
+ * @see https://kotlinlang.org/docs/dokka-module-and-package-docs.html
+ */
+ val includes: Set<String> = emptySet(),
+
+ /**
+ * A set of Kotlin files with functions that show how to use certain API.
+ *
+ * Contains paths relative to the root of [TestProject], so it must begin with `/`.
+ *
+ * Example: `/samples/collectionSamples.kt`
+ *
+ * @see DokkaConfiguration.DokkaSourceSet.samples
+ */
+ val samples: Set<String> = emptySet(),
+
+ /**
+ * Compatibility mode for Kotlin language version X.Y.
+ *
+ * Example: `1.9`
+ *
+ * @see https://kotlinlang.org/docs/compatibility-modes.html
+ */
+ val languageVersion: String? = null,
+
+ /**
+ * Compatibility mode for Kotlin API version X.Y.
+ *
+ * Example: `1.9`
+ *
+ * @see https://kotlinlang.org/docs/compatibility-modes.html
+ */
+ val apiVersion: String? = null,
+
+
+) {
+ override fun toString(): String {
+ return "TestDokkaSourceSet(" +
+ "analysisPlatform=$analysisPlatform, " +
+ "sourceSetID=$sourceSetID, " +
+ "dependentSourceSets=$dependentSourceSets, " +
+ "sourceRoots=$sourceRoots, " +
+ "classpath=$classpath, " +
+ "includes=$includes, " +
+ "samples=$samples, " +
+ "languageVersion=$languageVersion, " +
+ "apiVersion=$apiVersion" +
+ ")"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt
new file mode 100644
index 00000000..b6563fb7
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.configuration
+
+import org.jetbrains.dokka.analysis.test.api.TestProject
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * A builder for [TestDokkaConfiguration] that contains base options.
+ *
+ * Implementations can override the properties to modify the resulting test
+ * data or prohibit setting certain properties.
+ */
+@AnalysisTestDslMarker
+abstract class BaseTestDokkaConfigurationBuilder {
+
+ /**
+ * @see TestDokkaConfiguration.moduleName
+ */
+ abstract var moduleName: String
+
+
+ /**
+ * @see TestDokkaConfiguration.includes
+ */
+ open var includes: Set<String> = emptySet()
+
+ /**
+ * Verifies that this source set configuration is valid. For example,
+ * it should check that all file paths are set in the expected format.
+ *
+ * Must be invoked manually as part of [TestProject.verify].
+ */
+ open fun verify() {
+ includes.forEach {
+ verifyFilePathStartsWithSlash("includes", it)
+ verifyFileExtension("includes", it, ".md")
+ }
+ }
+
+ abstract fun build(): TestDokkaConfiguration
+
+ override fun toString(): String {
+ return "BaseTestDokkaConfigurationBuilder(moduleName='$moduleName', includes=$includes)"
+ }
+}
+
+/**
+ * A builder for [TestDokkaSourceSet] that contains base options.
+ *
+ * Implementations can override the properties to modify the resulting test
+ * data or prohibit setting certain properties.
+ */
+@AnalysisTestDslMarker
+abstract class BaseTestDokkaSourceSetBuilder {
+
+ /**
+ * Directories **additional** to the default source roots.
+ *
+ * @see TestDokkaSourceSet.sourceRoots
+ */
+ open var additionalSourceRoots: Set<String> = emptySet()
+
+ /**
+ * JARs **additional** to the default classpath.
+ *
+ * @see TestDokkaSourceSet.classpath
+ */
+ open var additionalClasspath: Set<String> = emptySet()
+
+ /**
+ * @see TestDokkaSourceSet.includes
+ */
+ open var includes: Set<String> = emptySet()
+
+ /**
+ * @see TestDokkaSourceSet.samples
+ */
+ open var samples: Set<String> = emptySet()
+
+ /**
+ * @see TestDokkaSourceSet.languageVersion
+ */
+ open var languageVersion: String? = null
+
+ /**
+ * @see TestDokkaSourceSet.apiVersion
+ */
+ open var apiVersion: String? = null
+
+ /**
+ * Verifies that this source set configuration is valid. For example,
+ * it should check that all file paths are set in the expected format.
+ *
+ * Must be invoked manually during the verification of the
+ * higher-level [BaseTestDokkaConfigurationBuilder.verify].
+ */
+ open fun verify() {
+ additionalSourceRoots.forEach {
+ verifyFilePathStartsWithSlash("additionalSourceRoots", it)
+ }
+ additionalClasspath.forEach {
+ // this check can be extended to accept .klib, .class or other files
+ // as the need for it arises, as long as Dokka supports it
+ verifyFileExtension("additionalClasspath", it, ".jar")
+ }
+ includes.forEach {
+ verifyFilePathStartsWithSlash("includes", it)
+ verifyFileExtension("includes", it, ".md")
+ }
+ samples.forEach {
+ verifyFilePathStartsWithSlash("samples", it)
+ verifyFileExtension("samples", it, ".kt")
+ }
+ }
+
+ abstract fun build(): TestDokkaSourceSet
+
+ override fun toString(): String {
+ return "BaseTestDokkaSourceSetBuilder(" +
+ "additionalSourceRoots=$additionalSourceRoots, " +
+ "additionalClasspath=$additionalClasspath, " +
+ "includes=$includes, " +
+ "samples=$samples, " +
+ "languageVersion=$languageVersion, " +
+ "apiVersion=$apiVersion" +
+ ")"
+ }
+}
+
+internal fun verifyFilePathStartsWithSlash(propertyName: String, path: String) {
+ require(path.startsWith("/")) {
+ "Property $propertyName must contain paths relative to the root of the project. " +
+ "Please, prefix it with \"/\" for readability and consistency."
+ }
+}
+
+internal fun verifyFileExtension(propertyName: String, filePath: String, expectedExtension: String) {
+ require(filePath.endsWith(expectedExtension)) {
+ "Property $propertyName only accepts files with \"$expectedExtension\" extension. " +
+ "Got: \"${filePath.substringAfterLast("/")}\"."
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt
new file mode 100644
index 00000000..f6356ab2
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationMapper.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.configuration
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.test.api.util.mapToSet
+import java.io.File
+
+/**
+ * Maps [TestDokkaConfiguration] to the actual [DokkaConfiguration] that will
+ * be used to run Dokka; The resulting configuration must be valid from Dokka's perspective.
+ *
+ * @receiver test configuration to map to the real one; all file paths must be relative to the
+ * root of the project.
+ * @param projectDir the actual project directory that will be used to create test files in;
+ * the path must be absolute and must exist.
+ */
+fun TestDokkaConfiguration.toDokkaConfiguration(projectDir: File): DokkaConfiguration {
+ require(projectDir.exists() && projectDir.isDirectory) {
+ "Expected the \"projectDir\" File param to exist and be a directory"
+ }
+
+ val moduleName = this.moduleName
+ val includes = this.includes.mapToSet { it.relativeTo(projectDir) }
+ val sourceSets = this.sourceSets.map { it.toDokkaSourceSet(projectDir) }
+
+ return object : DokkaConfiguration {
+
+ /*
+ * NOTE: The getters need to return data that can be compared by value.
+ * This means you can't recreate lists of interfaces on every invocation
+ * as their equals will return false, leading to difficult to trace bugs,
+ * especially when it comes to `SourceSetDependent<T>`
+ */
+
+ override val moduleName: String
+ get() = moduleName
+
+ override val includes: Set<File>
+ get() = includes
+
+ override val sourceSets: List<DokkaConfiguration.DokkaSourceSet>
+ get() = sourceSets
+
+ /*
+ * The plugin API uses the properties below to initialize plugins found on classpath.
+ * They are not settable directly in analysis tests, but must not throw any exceptions.
+ */
+ override val pluginsClasspath: List<File>
+ get() = emptyList()
+ override val pluginsConfiguration: List<DokkaConfiguration.PluginConfiguration>
+ get() = emptyList()
+
+ /*
+ * The properties below are not used by the analysis modules,
+ * and thus they don't need to be supported.
+ *
+ * If one of the properties below starts being used during
+ * analysis (i.e starts throwing an exception), a corresponding
+ * test property should be added along with the mapping.
+ */
+ override val moduleVersion: String
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val outputDir: File
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val cacheRoot: File
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val offlineMode: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val failOnWarning: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val modules: List<DokkaConfiguration.DokkaModuleDescription>
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val delayTemplateSubstitution: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val suppressObviousFunctions: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val suppressInheritedMembers: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val finalizeCoroutines: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ }
+}
+
+private fun TestDokkaSourceSet.toDokkaSourceSet(relativeToDir: File): DokkaConfiguration.DokkaSourceSet {
+ val analysisPlatform = this.analysisPlatform
+ val sourceSetID = this.sourceSetID
+ val dependentSourceSets = this.dependentSourceSets
+ val sourceRoots = this.sourceRoots.mapToSet { it.relativeTo(relativeToDir) }
+ val classpath = this.classpath.map { File(it) }
+ val includes = this.includes.mapToSet { it.relativeTo(relativeToDir) }
+ val samples = this.samples.mapToSet { it.relativeTo(relativeToDir) }
+ val languageVersion = this.languageVersion
+ val apiVersion = this.apiVersion
+
+ return object : DokkaConfiguration.DokkaSourceSet {
+
+ /*
+ * NOTE: The getters need to return data that can be compared by value.
+ * This means you can't recreate lists of interfaces on every invocation
+ * as their equals will return false, leading to difficult to trace bugs,
+ * especially when it comes to `SourceSetDependent<T>`
+ */
+
+ override val analysisPlatform: Platform
+ get() = analysisPlatform
+
+ override val sourceSetID: DokkaSourceSetID
+ get() = sourceSetID
+
+ override val dependentSourceSets: Set<DokkaSourceSetID>
+ get() = dependentSourceSets
+
+ override val sourceRoots: Set<File>
+ get() = sourceRoots
+
+ override val classpath: List<File>
+ get() = classpath
+
+ override val includes: Set<File>
+ get() = includes
+
+ override val samples: Set<File>
+ get() = samples
+
+ override val languageVersion: String?
+ get() = languageVersion
+
+ override val apiVersion: String?
+ get() = apiVersion
+
+
+ /*
+ * The properties below are not used by the analysis modules,
+ * and thus they don't need to be supported.
+ *
+ * If one of the properties below starts being used during
+ * analysis (i.e starts throwing an exception), a corresponding
+ * test property should be added along with the mapping.
+ */
+ override val displayName: String
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ @Suppress("OVERRIDE_DEPRECATION")
+ override val includeNonPublic: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val reportUndocumented: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val skipEmptyPackages: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val skipDeprecated: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val jdkVersion: Int
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val sourceLinks: Set<DokkaConfiguration.SourceLinkDefinition>
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val perPackageOptions: List<DokkaConfiguration.PackageOptions>
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val externalDocumentationLinks: Set<DokkaConfiguration.ExternalDocumentationLink>
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val noStdlibLink: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val noJdkLink: Boolean
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val suppressedFiles: Set<File>
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ override val documentedVisibilities: Set<DokkaConfiguration.Visibility>
+ get() = throw NotImplementedError("Not expected to be used by analysis modules")
+ }
+}
+
+private fun String.relativeTo(dir: File): File {
+ return dir.resolve(this.removePrefix("/"))
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt
new file mode 100644
index 00000000..6775fa21
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaConfigurationBuilder.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.java
+
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaSourceSetBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * An implementation of [BaseTestDokkaConfigurationBuilder] specific to [JavaTestProject].
+ */
+class JavaTestConfigurationBuilder : BaseTestDokkaConfigurationBuilder() {
+ override var moduleName: String = "javaTestProject"
+
+ private val javaSourceSetBuilder = JavaTestSourceSetBuilder()
+
+ @AnalysisTestDslMarker
+ fun javaSourceSet(fillSourceSet: JavaTestSourceSetBuilder.() -> Unit) {
+ fillSourceSet(javaSourceSetBuilder)
+ }
+
+ override fun verify() {
+ super.verify()
+ javaSourceSetBuilder.verify()
+ }
+
+ override fun build(): TestDokkaConfiguration {
+ return TestDokkaConfiguration(
+ moduleName = moduleName,
+ includes = includes,
+ sourceSets = setOf(javaSourceSetBuilder.build())
+ )
+ }
+}
+
+/**
+ * An implementation of [BaseTestDokkaSourceSetBuilder] specific to [JavaTestProject].
+ *
+ * Defines sensible defaults that should cover the majority of simple projects.
+ */
+class JavaTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() {
+ override fun build(): TestDokkaSourceSet {
+ return TestDokkaSourceSet(
+ analysisPlatform = Platform.jvm,
+ sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "java"),
+ dependentSourceSets = setOf(),
+ sourceRoots = additionalSourceRoots + setOf(JavaTestProject.DEFAULT_SOURCE_ROOT),
+ classpath = additionalClasspath, // TODO [beresnev] is kotlin jvm stdlib needed here?
+ includes = includes,
+ samples = samples,
+ languageVersion = languageVersion,
+ apiVersion = apiVersion
+ )
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt
new file mode 100644
index 00000000..2a79210f
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaFileCreator.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.java
+
+/**
+ * Declares a capability that a `.java` file can be created in the scope of the implementation.
+ */
+interface JavaFileCreator {
+
+ /**
+ * Creates a `.java` file.
+ *
+ * By default, the package of this file is deduced automatically from the [pathFromSrc] param.
+ * For example, for a path `org/jetbrains/dokka/test` the package will be `org.jetbrains.dokka.test`.
+ * It is normally prohibited for Java files to have a mismatch in package and file path, so it
+ * cannot be overridden.
+ *
+ * @param pathFromSrc path relative to the source code directory of the project.
+ * Must contain packages (if any) and end in `.java`.
+ * Example: `org/jetbrains/dokka/test/MyClass.java`
+ */
+ fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit)
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt
new file mode 100644
index 00000000..48705e78
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestData.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.java
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+import org.jetbrains.dokka.analysis.test.api.util.filePathToPackageName
+
+/**
+ * A container for populating and holding Java source code test data.
+ *
+ * This container exists so that common creation, population and verification logic
+ * can be reused, instead of having to implement [JavaFileCreator] multiple times.
+ *
+ * @param pathToJavaSources path to the `src` directory in which Java sources must reside.
+ * Must be relative to the root of the test project. Example: `/src/main/java`
+ * @see TestData
+ */
+class JavaTestData(
+ private val pathToJavaSources: String
+) : TestData, JavaFileCreator {
+
+ private val files = mutableListOf<TestDataFile>()
+
+ override fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) {
+ val fileName = pathFromSrc.substringAfterLast("/")
+ check(fileName.endsWith(".java")) { "Java files are expected to have .java extension" }
+
+ val testDataFile = JavaTestDataFile(
+ fullyQualifiedPackageName = filePathToPackageName(pathFromSrc),
+ pathFromProjectRoot = "$pathToJavaSources/$pathFromSrc",
+ )
+ fillFile(testDataFile)
+ testDataFile.checkFileNameIsPresent(fileName)
+ files.add(testDataFile)
+ }
+
+ private fun JavaTestDataFile.checkFileNameIsPresent(fileName: String) {
+ check(this.getContents().contains(fileName.removeSuffix(".java"))) {
+ "Expected the .java file name to be the same as the top-level declaration name (class/interface)."
+ }
+ }
+
+ override fun getFiles(): List<TestDataFile> {
+ return files
+ }
+
+ override fun toString(): String {
+ return "JavaTestData(pathToJavaSources='$pathToJavaSources', files=$files)"
+ }
+}
+
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt
new file mode 100644
index 00000000..d8cc86a8
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestDataFile.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.java
+
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+class JavaTestDataFile(
+ fullyQualifiedPackageName: String,
+ pathFromProjectRoot: String,
+) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) {
+
+ private var fileContents = "package $fullyQualifiedPackageName;" + System.lineSeparator().repeat(2)
+
+ operator fun String.unaryPlus() {
+ fileContents += (this.trimIndent() + System.lineSeparator())
+ }
+
+ override fun getContents(): String {
+ return fileContents
+ }
+
+ override fun toString(): String {
+ return "JavaTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt
new file mode 100644
index 00000000..39f0f0f6
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/java/JavaTestProject.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.java
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+import org.jetbrains.dokka.analysis.test.api.TestProject
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.javaTestProject
+import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestData
+import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile
+import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+import org.jetbrains.dokka.analysis.test.api.util.flatListOf
+
+/**
+ * @see javaTestProject for an explanation and a convenient way to construct this project
+ */
+class JavaTestProject : TestProject, JavaFileCreator, MdFileCreator {
+
+ private val projectConfigurationBuilder = JavaTestConfigurationBuilder()
+
+ private val javaSourceSet = JavaTestData(pathToJavaSources = DEFAULT_SOURCE_ROOT)
+ private val markdownTestData = MarkdownTestData()
+
+ @AnalysisTestDslMarker
+ fun dokkaConfiguration(fillConfiguration: JavaTestConfigurationBuilder.() -> Unit) {
+ fillConfiguration(projectConfigurationBuilder)
+ }
+
+ @AnalysisTestDslMarker
+ override fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) {
+ javaSourceSet.javaFile(pathFromSrc, fillFile)
+ }
+
+ @AnalysisTestDslMarker
+ override fun mdFile(pathFromProjectRoot: String, fillFile: MarkdownTestDataFile.() -> Unit) {
+ markdownTestData.mdFile(pathFromProjectRoot, fillFile)
+ }
+
+ override fun verify() {
+ projectConfigurationBuilder.verify()
+ }
+
+ override fun getConfiguration(): TestDokkaConfiguration {
+ return projectConfigurationBuilder.build()
+ }
+
+ override fun getTestData(): TestData {
+ return object : TestData {
+ override fun getFiles(): List<TestDataFile> {
+ return flatListOf(
+ this@JavaTestProject.javaSourceSet.getFiles(),
+ this@JavaTestProject.markdownTestData.getFiles()
+ )
+ }
+ }
+ }
+
+ override fun toString(): String {
+ return "JavaTestProject(" +
+ "projectConfigurationBuilder=$projectConfigurationBuilder, " +
+ "javaSourceSet=$javaSourceSet, " +
+ "markdownTestData=$markdownTestData" +
+ ")"
+ }
+
+ internal companion object {
+ internal const val DEFAULT_SOURCE_ROOT = "/src/main/java"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt
new file mode 100644
index 00000000..a0255611
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmConfigurationBuilder.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.kotlin
+
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaSourceSetBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * An implementation of [BaseTestDokkaConfigurationBuilder] specific to [KotlinJvmTestProject].
+ */
+class KotlinJvmTestConfigurationBuilder : BaseTestDokkaConfigurationBuilder() {
+ override var moduleName: String = "kotlinJvmTestProject"
+
+ private val kotlinSourceSetBuilder = KotlinJvmTestSourceSetBuilder()
+
+ @AnalysisTestDslMarker
+ fun kotlinSourceSet(fillSourceSet: KotlinJvmTestSourceSetBuilder.() -> Unit) {
+ fillSourceSet(kotlinSourceSetBuilder)
+ }
+
+ override fun verify() {
+ super.verify()
+ kotlinSourceSetBuilder.verify()
+ }
+
+ override fun build(): TestDokkaConfiguration {
+ return TestDokkaConfiguration(
+ moduleName = moduleName,
+ includes = includes,
+ sourceSets = setOf(kotlinSourceSetBuilder.build())
+ )
+ }
+}
+
+class KotlinJvmTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() {
+ override fun build(): TestDokkaSourceSet {
+ return TestDokkaSourceSet(
+ analysisPlatform = Platform.jvm,
+ sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "kotlin"),
+ dependentSourceSets = setOf(),
+ sourceRoots = additionalSourceRoots + setOf(KotlinJvmTestProject.DEFAULT_SOURCE_ROOT),
+ classpath = additionalClasspath + setOf(getKotlinJvmStdlibJarPath()),
+ includes = includes,
+ samples = samples,
+ languageVersion = languageVersion,
+ apiVersion = apiVersion
+ )
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt
new file mode 100644
index 00000000..c90e126f
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmDependencyUtils.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.kotlin
+
+private val lazyKotlinJvmStdlibJar by lazy {
+ ClassLoader.getSystemResource("kotlin/jvm/Strictfp.class")
+ ?.file
+ ?.replace("file:", "")
+ ?.replaceAfter(".jar", "")
+ ?: error("Unable to find Kotlin's JVM stdlib jar.")
+}
+
+/**
+ * Returns an absolute path to the JAR file of Kotlin's standard library for the JVM platform.
+ *
+ * Example: `~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.9.10/xx/kotlin-stdlib-1.9.10.jar`
+ */
+internal fun getKotlinJvmStdlibJarPath(): String {
+ return lazyKotlinJvmStdlibJar
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt
new file mode 100644
index 00000000..178a1dc3
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/kotlin/KotlinJvmTestProject.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.kotlin
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+import org.jetbrains.dokka.analysis.test.api.TestProject
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestData
+import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestDataFile
+import org.jetbrains.dokka.analysis.test.api.kotlin.KtFileCreator
+import org.jetbrains.dokka.analysis.test.api.kotlin.sample.KotlinSampleFileCreator
+import org.jetbrains.dokka.analysis.test.api.kotlin.sample.KotlinSampleTestData
+import org.jetbrains.dokka.analysis.test.api.kotlin.sample.KotlinSampleTestDataFile
+import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestData
+import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile
+import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+import org.jetbrains.dokka.analysis.test.api.util.flatListOf
+
+/**
+ * @see kotlinJvmTestProject for an explanation and a convenient way to construct this project
+ */
+class KotlinJvmTestProject : TestProject, KtFileCreator, MdFileCreator, KotlinSampleFileCreator {
+
+ private val projectConfigurationBuilder = KotlinJvmTestConfigurationBuilder()
+
+ private val kotlinSourceSet = KotlinTestData(pathToKotlinSources = DEFAULT_SOURCE_ROOT)
+ private val markdownTestData = MarkdownTestData()
+ private val kotlinSampleTestData = KotlinSampleTestData()
+
+ @AnalysisTestDslMarker
+ fun dokkaConfiguration(fillConfiguration: KotlinJvmTestConfigurationBuilder.() -> Unit) {
+ fillConfiguration(projectConfigurationBuilder)
+ }
+
+ @AnalysisTestDslMarker
+ override fun ktFile(pathFromSrc: String, fqPackageName: String, fillFile: KotlinTestDataFile.() -> Unit) {
+ kotlinSourceSet.ktFile(pathFromSrc, fqPackageName, fillFile)
+ }
+
+ @AnalysisTestDslMarker
+ override fun mdFile(pathFromProjectRoot: String, fillFile: MarkdownTestDataFile.() -> Unit) {
+ markdownTestData.mdFile(pathFromProjectRoot, fillFile)
+ }
+
+ @AnalysisTestDslMarker
+ override fun sampleFile(
+ pathFromProjectRoot: String,
+ fqPackageName: String,
+ fillFile: KotlinSampleTestDataFile.() -> Unit
+ ) {
+ kotlinSampleTestData.sampleFile(pathFromProjectRoot, fqPackageName, fillFile)
+ }
+
+ override fun verify() {
+ projectConfigurationBuilder.verify()
+ }
+
+ override fun getConfiguration(): TestDokkaConfiguration {
+ return projectConfigurationBuilder.build()
+ }
+
+ override fun getTestData(): TestData {
+ return object : TestData {
+ override fun getFiles(): List<TestDataFile> {
+ return flatListOf(
+ this@KotlinJvmTestProject.kotlinSourceSet.getFiles(),
+ this@KotlinJvmTestProject.markdownTestData.getFiles(),
+ this@KotlinJvmTestProject.kotlinSampleTestData.getFiles()
+ )
+ }
+ }
+ }
+
+ override fun toString(): String {
+ return "KotlinJvmTestProject(" +
+ "projectConfigurationBuilder=$projectConfigurationBuilder, " +
+ "kotlinSourceSet=$kotlinSourceSet, " +
+ "markdownTestData=$markdownTestData, " +
+ "kotlinSampleTestData=$kotlinSampleTestData" +
+ ")"
+ }
+
+ internal companion object {
+ internal const val DEFAULT_SOURCE_ROOT = "/src/main/kotlin"
+ }
+}
+
+
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt
new file mode 100644
index 00000000..16af6e7d
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmConfigurationBuilder.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.mixed
+
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaConfigurationBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.BaseTestDokkaSourceSetBuilder
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet
+import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject
+import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.getKotlinJvmStdlibJarPath
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * An implementation of [BaseTestDokkaConfigurationBuilder] specific to [MixedJvmTestProject].
+ */
+class MixedJvmTestConfigurationBuilder : BaseTestDokkaConfigurationBuilder() {
+ override var moduleName: String = "mixedJvmTestProject"
+
+ private val mixedJvmSourceSetBuilder = MixedJvmTestSourceSetBuilder()
+
+ @AnalysisTestDslMarker
+ fun jvmSourceSet(fillSourceDir: MixedJvmTestSourceSetBuilder.() -> Unit) {
+ fillSourceDir(mixedJvmSourceSetBuilder)
+ }
+
+ override fun verify() {
+ super.verify()
+ mixedJvmSourceSetBuilder.verify()
+ }
+
+ override fun build(): TestDokkaConfiguration {
+ return TestDokkaConfiguration(
+ moduleName = moduleName,
+ includes = includes,
+ sourceSets = setOf(mixedJvmSourceSetBuilder.build())
+ )
+ }
+
+ override fun toString(): String {
+ return "MixedJvmTestConfigurationBuilder(" +
+ "moduleName='$moduleName', " +
+ "jvmSourceSetBuilder=$mixedJvmSourceSetBuilder" +
+ ")"
+ }
+}
+
+class MixedJvmTestSourceSetBuilder : BaseTestDokkaSourceSetBuilder() {
+ override fun build(): TestDokkaSourceSet {
+ return TestDokkaSourceSet(
+ analysisPlatform = Platform.jvm,
+ sourceSetID = DokkaSourceSetID(scopeId = "project", sourceSetName = "jvm"),
+ dependentSourceSets = setOf(),
+ sourceRoots = additionalSourceRoots + setOf(
+ KotlinJvmTestProject.DEFAULT_SOURCE_ROOT,
+ JavaTestProject.DEFAULT_SOURCE_ROOT
+ ),
+ classpath = additionalClasspath + setOf(getKotlinJvmStdlibJarPath()),
+ includes = includes,
+ samples = samples,
+ languageVersion = languageVersion,
+ apiVersion = apiVersion
+ )
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt
new file mode 100644
index 00000000..24889fe2
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestData.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.mixed
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaFileCreator
+import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestData
+import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestDataFile
+import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestData
+import org.jetbrains.dokka.analysis.test.api.kotlin.KotlinTestDataFile
+import org.jetbrains.dokka.analysis.test.api.kotlin.KtFileCreator
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+
+/**
+ * A container for populating and holding Kotlin and Java source code test data.
+ *
+ * This container exists so that common creation, population and verification logic
+ * can be reused, instead of having to implement [KtFileCreator] and [JavaFileCreator] multiple times.
+ *
+ * @see TestData
+ */
+class MixedJvmTestData(pathToSources: String) : TestData, KtFileCreator, JavaFileCreator {
+
+ private val javaTestData = JavaTestData(pathToJavaSources = pathToSources)
+ private val kotlinJvmTestData = KotlinTestData(pathToKotlinSources = pathToSources)
+
+ @AnalysisTestDslMarker
+ override fun javaFile(pathFromSrc: String, fillFile: JavaTestDataFile.() -> Unit) {
+ javaTestData.javaFile(pathFromSrc, fillFile)
+ }
+
+ @AnalysisTestDslMarker
+ override fun ktFile(pathFromSrc: String, fqPackageName: String, fillFile: KotlinTestDataFile.() -> Unit) {
+ kotlinJvmTestData.ktFile(pathFromSrc, fqPackageName, fillFile)
+ }
+
+ override fun getFiles(): List<TestDataFile> {
+ return javaTestData.getFiles() + kotlinJvmTestData.getFiles()
+ }
+
+ override fun toString(): String {
+ return "MixedJvmTestData(javaTestData=$javaTestData, kotlinJvmTestData=$kotlinJvmTestData)"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt
new file mode 100644
index 00000000..32b7ce0a
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/jvm/mixed/MixedJvmTestProject.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.jvm.mixed
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+import org.jetbrains.dokka.analysis.test.api.TestProject
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.jvm.java.JavaTestProject
+import org.jetbrains.dokka.analysis.test.api.jvm.kotlin.KotlinJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestData
+import org.jetbrains.dokka.analysis.test.api.markdown.MarkdownTestDataFile
+import org.jetbrains.dokka.analysis.test.api.markdown.MdFileCreator
+import org.jetbrains.dokka.analysis.test.api.mixedJvmTestProject
+import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker
+import org.jetbrains.dokka.analysis.test.api.util.flatListOf
+
+/**
+ * @see mixedJvmTestProject for an explanation and a convenient way to construct this project
+ */
+class MixedJvmTestProject : TestProject, MdFileCreator {
+
+ private val projectConfigurationBuilder = MixedJvmTestConfigurationBuilder()
+
+ private val kotlinSourceDirectory = MixedJvmTestData(pathToSources = KotlinJvmTestProject.DEFAULT_SOURCE_ROOT)
+ private val javaSourceDirectory = MixedJvmTestData(pathToSources = JavaTestProject.DEFAULT_SOURCE_ROOT)
+ private val markdownTestData = MarkdownTestData()
+
+ @AnalysisTestDslMarker
+ fun dokkaConfiguration(fillConfiguration: MixedJvmTestConfigurationBuilder.() -> Unit) {
+ fillConfiguration(projectConfigurationBuilder)
+ }
+
+ @AnalysisTestDslMarker
+ fun kotlinSourceDirectory(fillTestData: MixedJvmTestData.() -> Unit) {
+ fillTestData(kotlinSourceDirectory)
+ }
+
+ @AnalysisTestDslMarker
+ fun javaSourceDirectory(fillTestData: MixedJvmTestData.() -> Unit) {
+ fillTestData(javaSourceDirectory)
+ }
+
+ @AnalysisTestDslMarker
+ override fun mdFile(pathFromProjectRoot: String, fillFile: MarkdownTestDataFile.() -> Unit) {
+ markdownTestData.mdFile(pathFromProjectRoot, fillFile)
+ }
+
+ override fun verify() {
+ projectConfigurationBuilder.verify()
+ }
+
+ override fun getConfiguration(): TestDokkaConfiguration {
+ return projectConfigurationBuilder.build()
+ }
+
+ override fun getTestData(): TestData {
+ return object : TestData {
+ override fun getFiles(): List<TestDataFile> {
+ return flatListOf(
+ this@MixedJvmTestProject.kotlinSourceDirectory.getFiles(),
+ this@MixedJvmTestProject.javaSourceDirectory.getFiles(),
+ this@MixedJvmTestProject.markdownTestData.getFiles()
+ )
+ }
+ }
+ }
+
+ override fun toString(): String {
+ return "MixedJvmTestProject(" +
+ "projectConfigurationBuilder=$projectConfigurationBuilder, " +
+ "kotlinSourceDirectory=$kotlinSourceDirectory, " +
+ "javaSourceDirectory=$javaSourceDirectory, " +
+ "markdownTestData=$markdownTestData" +
+ ")"
+ }
+}
+
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.kt
new file mode 100644
index 00000000..02911235
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestData.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.analysis.test.api.kotlin
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+/**
+ * A container for populating and holding Kotlin source code test data.
+ *
+ * This container exists so that common creation, population and verification logic
+ * can be reused, instead of having to implement [KtFileCreator] multiple times.
+ *
+ * @param pathToKotlinSources path to the `src` directory in which Kotlin sources must reside.
+ * Must be relative to the root of the test project. Example: `/src/main/kotlin`
+ * @see TestData
+ */
+class KotlinTestData(
+ private val pathToKotlinSources: String
+) : TestData, KtFileCreator {
+
+ private val files = mutableListOf<TestDataFile>()
+
+ override fun ktFile(
+ pathFromSrc: String,
+ fqPackageName: String,
+ fillFile: KotlinTestDataFile.() -> Unit
+ ) {
+ check(pathFromSrc.endsWith(".kt")) { "Kotlin files are expected to have .kt extension" }
+
+ val testDataFile = KotlinTestDataFile(
+ pathFromProjectRoot = "$pathToKotlinSources/$pathFromSrc",
+ fqPackageName = fqPackageName,
+ )
+ fillFile(testDataFile)
+ files.add(testDataFile)
+ }
+
+ override fun getFiles(): List<TestDataFile> {
+ return files
+ }
+
+ override fun toString(): String {
+ return "KotlinTestData(pathToKotlinSources='$pathToKotlinSources', files=$files)"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt
new file mode 100644
index 00000000..355440e7
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KotlinTestDataFile.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.kotlin
+
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+class KotlinTestDataFile(
+ pathFromProjectRoot: String,
+ fqPackageName: String,
+) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) {
+
+ private var fileContents = "package $fqPackageName" + System.lineSeparator().repeat(2)
+
+ operator fun String.unaryPlus() {
+ fileContents += (this.trimIndent() + System.lineSeparator())
+ }
+
+ override fun getContents(): String {
+ return fileContents
+ }
+
+ override fun toString(): String {
+ return "KotlinTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt
new file mode 100644
index 00000000..15ce31ce
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/KtFileCreator.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.kotlin
+
+import org.jetbrains.dokka.analysis.test.api.util.filePathToPackageName
+
+/**
+ * Declares a capability that a `.kt` file can be created in the scope of the implementation.
+ */
+interface KtFileCreator {
+
+ /**
+ * Creates a `.kt` file.
+ *
+ * By default, the package of this file is deduced automatically from the [pathFromSrc] param.
+ * For example, for a path `org/jetbrains/dokka/test` the package will be `org.jetbrains.dokka.test`.
+ * The package can be overridden by setting [fqPackageName].
+ *
+ * @param pathFromSrc path relative to the source code directory of the project.
+ * Must contain packages (if any) and end in `.kt`.
+ * Example: `org/jetbrains/dokka/test/File.kt`
+ * @param fqPackageName package name to be used in the `package` statement of this file.
+ * This value overrides the automatically deduced package.
+ */
+ fun ktFile(
+ pathFromSrc: String,
+ fqPackageName: String = filePathToPackageName(pathFromSrc),
+ fillFile: KotlinTestDataFile.() -> Unit
+ )
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt
new file mode 100644
index 00000000..7fa1d28d
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleFileCreator.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.kotlin.sample
+
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet
+
+/**
+ * Declares a capability that a `.kt` file can be created that will be used as a usage sample.
+ */
+interface KotlinSampleFileCreator {
+
+ /**
+ * Creates a `.kt` file outside of the source code directory. It should be used as input
+ * for the `@sample` KDoc tag.
+ *
+ * To be picked by Dokka, this file must be included in [TestDokkaSourceSet.samples].
+ *
+ * @param pathFromProjectRoot path relative to the root of the test project. Must begin
+ * with `/` to not confuse it with relative paths. Example: `/samples/collections.kt`
+ * @param fqPackageName fully qualified package name to be used in the `package` statement of the file.
+ * This parameter must be set because the package name cannot be deduced
+ * from the file path, as samples usually reside outside of the source code directory.
+ * Example: `org.jetbrains.dokka.sample.collections`
+ */
+ fun sampleFile(
+ pathFromProjectRoot: String,
+ fqPackageName: String,
+ fillFile: KotlinSampleTestDataFile.() -> Unit
+ )
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt
new file mode 100644
index 00000000..9ba4187c
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestData.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.kotlin.sample
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+/**
+ * A container for populating and holding Kotlin sample test data.
+ *
+ * This container exists so that common creation, population and verification logic
+ * can be reused, instead of having to implement [KotlinSampleFileCreator] multiple times.
+ *
+ * @see TestData
+ */
+class KotlinSampleTestData : TestData, KotlinSampleFileCreator {
+
+ private val files = mutableListOf<TestDataFile>()
+
+ override fun sampleFile(
+ pathFromProjectRoot: String,
+ fqPackageName: String,
+ fillFile: KotlinSampleTestDataFile.() -> Unit
+ ) {
+ check(pathFromProjectRoot.endsWith(".kt")) { "Kotlin sample files are expected to have .kt extension" }
+
+ val testDataFile = KotlinSampleTestDataFile(
+ pathFromProjectRoot = pathFromProjectRoot,
+ fqPackageName = fqPackageName
+ )
+ fillFile(testDataFile)
+ files.add(testDataFile)
+ }
+
+ override fun getFiles(): List<TestDataFile> {
+ return files
+ }
+
+ override fun toString(): String {
+ return "KotlinSampleTestData(files=$files)"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt
new file mode 100644
index 00000000..6dc8230b
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/kotlin/sample/KotlinSampleTestDataFile.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.kotlin.sample
+
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+class KotlinSampleTestDataFile(
+ pathFromProjectRoot: String,
+ fqPackageName: String,
+) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) {
+
+ private var fileContents = "package $fqPackageName" + System.lineSeparator().repeat(2)
+
+ operator fun String.unaryPlus() {
+ fileContents += (this.trimIndent() + System.lineSeparator())
+ }
+
+ override fun getContents(): String {
+ return fileContents
+ }
+
+ override fun toString(): String {
+ return "KotlinSampleTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt
new file mode 100644
index 00000000..d0cb64fb
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestData.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.markdown
+
+import org.jetbrains.dokka.analysis.test.api.TestData
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+/**
+ * A container for populating and holding Markdown test data.
+ *
+ * This container exists so that common creation, population and verification logic
+ * can be reused, instead of having to implement [MdFileCreator] multiple times.
+ *
+ * @see TestData
+ */
+class MarkdownTestData : TestData, MdFileCreator {
+
+ private val files = mutableListOf<TestDataFile>()
+
+ override fun mdFile(
+ pathFromProjectRoot: String,
+ fillFile: MarkdownTestDataFile.() -> Unit
+ ) {
+ check(pathFromProjectRoot.endsWith(".md")) { "Markdown files are expected to have .md extension" }
+
+ val testDataFile = MarkdownTestDataFile(pathFromProjectRoot)
+ fillFile(testDataFile)
+ files.add(testDataFile)
+ }
+
+ override fun getFiles(): List<TestDataFile> {
+ return files
+ }
+
+ override fun toString(): String {
+ return "MarkdownTestData(files=$files)"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt
new file mode 100644
index 00000000..9bb8169a
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MarkdownTestDataFile.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.markdown
+
+import org.jetbrains.dokka.analysis.test.api.TestDataFile
+
+class MarkdownTestDataFile(
+ pathFromProjectRoot: String
+) : TestDataFile(pathFromProjectRoot = pathFromProjectRoot) {
+
+ private var fileContents = ""
+
+ operator fun String.unaryPlus() {
+ fileContents += (this.trimIndent() + System.lineSeparator())
+ }
+
+ override fun getContents(): String {
+ return fileContents
+ }
+
+ override fun toString(): String {
+ return "MarkdownTestDataFile(pathFromProjectRoot='$pathFromProjectRoot', fileContents='$fileContents')"
+ }
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt
new file mode 100644
index 00000000..8ba97b17
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/markdown/MdFileCreator.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.markdown
+
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaConfiguration
+import org.jetbrains.dokka.analysis.test.api.configuration.TestDokkaSourceSet
+
+/**
+ * Declares a capability that an `.md` file can be created in the scope of the implementation.
+ */
+interface MdFileCreator {
+
+ /**
+ * Creates an `.md` (Markdown) file.
+ *
+ * If you want to use this file for module and package documentation, it must be included
+ * in [TestDokkaConfiguration.includes] or [TestDokkaSourceSet.includes].
+ *
+ * @param pathFromProjectRoot path relative to the root of the test project. Must begin
+ * with `/` to not confuse it with relative paths. Example: `/docs/core-package.md`
+ */
+ fun mdFile(
+ pathFromProjectRoot: String,
+ fillFile: MarkdownTestDataFile.() -> Unit
+ )
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.kt
new file mode 100644
index 00000000..6cd41594
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/CollectionUtils.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.analysis.test.api.util
+
+internal inline fun <reified T> flatListOf(vararg lists: List<T>): List<T> {
+ val overallSize = lists.sumBy { it.size }
+ val aggregatingList = ArrayList<T>(overallSize)
+ lists.forEach {
+ aggregatingList.addAll(it)
+ }
+ return aggregatingList
+}
+
+internal inline fun <T, R> Iterable<T>.mapToSet(transform: (T) -> R): Set<R> {
+ return this.map(transform).toSet()
+}
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt
new file mode 100644
index 00000000..e855bb4b
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/DslApiUtils.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.util
+
+@DslMarker
+@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
+annotation class AnalysisTestDslMarker
diff --git a/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt
new file mode 100644
index 00000000..779add8d
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.test.api.util
+
+import org.jetbrains.dokka.utilities.DokkaLogger
+import java.io.File
+import java.io.IOException
+
+/**
+ * Converts a file path (like `org/jetbrains/dokka/test/File.kt`) to a fully qualified
+ * package name (like `org.jetbrains.dokka.test`).
+ *
+ * @param srcRelativeFilePath path relative to the source code directory. If the full path of the file
+ * is `src/main/kotlin/org/jetbrains/dokka/test/File.kt`, then only
+ * `org/jetbrains/dokka/test/File.kt` is expected to be passed here.
+ * @return fully qualified package name or an empty string in case of the root package.
+ */
+internal fun filePathToPackageName(srcRelativeFilePath: String): String {
+ return srcRelativeFilePath
+ .substringBeforeLast("/", missingDelimiterValue = "")
+ .replace("/", ".")
+}
+
+/**
+ * @throws IOException if the requested temporary directory could not be created or deleted once used.
+ */
+internal fun <T> withTempDirectory(logger: DokkaLogger? = null, block: (tempDirectory: File) -> T): T {
+ @Suppress("DEPRECATION") // TODO migrate to kotlin.io.path.createTempDirectory with languageVersion >= 1.5
+ val tempDir = createTempDir()
+ try {
+ logger?.debug("Created temporary directory $tempDir")
+ return block(tempDir)
+ } finally {
+ if (!tempDir.deleteRecursively()) {
+ throw IOException("Unable to delete temporary directory $tempDir")
+ }
+ logger?.debug("Deleted temporary directory $tempDir")
+ }
+}