aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/core-test-api/src/main
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-11-10 11:46:54 +0100
committerGitHub <noreply@github.com>2023-11-10 11:46:54 +0100
commit8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch)
tree1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-subprojects/core-test-api/src/main
parenta44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff)
downloaddokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.gz
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.bz2
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.zip
Restructure the project to utilize included builds (#3174)
* Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich <whyoleg@gmail.com>
Diffstat (limited to 'dokka-subprojects/core-test-api/src/main')
-rw-r--r--dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/context/MockContext.kt53
-rw-r--r--dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/logger/TestLogger.kt58
-rw-r--r--dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestDokkaConfigurationBuilder.kt219
-rw-r--r--dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestRunner.kt235
4 files changed, 565 insertions, 0 deletions
diff --git a/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/context/MockContext.kt b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/context/MockContext.kt
new file mode 100644
index 00000000..06ea2dad
--- /dev/null
+++ b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/context/MockContext.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.testApi.context
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.ExtensionPoint
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import kotlin.reflect.KClass
+import kotlin.reflect.KMutableProperty
+import kotlin.reflect.full.memberProperties
+
+@Suppress("UNCHECKED_CAST") // It is only usable from tests so we do not care about safety
+public class MockContext(
+ vararg extensions: Pair<ExtensionPoint<*>, (DokkaContext) -> Any>,
+ private val testConfiguration: DokkaConfiguration? = null,
+ private val unusedExtensionPoints: List<ExtensionPoint<*>>? = null
+) : DokkaContext {
+ private val extensionMap by lazy {
+ extensions.groupBy(Pair<ExtensionPoint<*>, (DokkaContext) -> Any>::first) {
+ it.second(this)
+ }
+ }
+
+ private val plugins = mutableMapOf<KClass<out DokkaPlugin>, DokkaPlugin>()
+
+ override fun <T : DokkaPlugin> plugin(kclass: KClass<T>): T = plugins.getOrPut(kclass) {
+ kclass.constructors.single { it.parameters.isEmpty() }.call().also { it.injectContext(this) }
+ } as T
+
+ override fun <T : Any, E : ExtensionPoint<T>> get(point: E): List<T> = extensionMap[point].orEmpty() as List<T>
+
+ override fun <T : Any, E : ExtensionPoint<T>> single(point: E): T = get(point).single()
+
+ override val logger: DokkaLogger = DokkaConsoleLogger(LoggingLevel.DEBUG)
+
+ override val configuration: DokkaConfiguration
+ get() = testConfiguration ?: throw IllegalStateException("This mock context doesn't provide configuration")
+
+ override val unusedPoints: Collection<ExtensionPoint<*>>
+ get() = unusedExtensionPoints
+ ?: throw IllegalStateException("This mock context doesn't provide unused extension points")
+}
+
+private fun DokkaPlugin.injectContext(context: DokkaContext) {
+ (DokkaPlugin::class.memberProperties.single { it.name == "context" } as KMutableProperty<*>)
+ .setter.call(this, context)
+}
diff --git a/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/logger/TestLogger.kt b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/logger/TestLogger.kt
new file mode 100644
index 00000000..c285a663
--- /dev/null
+++ b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/logger/TestLogger.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.testApi.logger
+
+import org.jetbrains.dokka.utilities.DokkaLogger
+import java.util.*
+
+/*
+ * Even in tests it be used in a concurrent environment, so needs to be thread safe
+ */
+public class TestLogger(private val logger: DokkaLogger) : DokkaLogger {
+ override var warningsCount: Int by logger::warningsCount
+ override var errorsCount: Int by logger::errorsCount
+
+ private var _debugMessages = synchronizedMutableListOf<String>()
+ public val debugMessages: List<String> get() = _debugMessages.toList()
+
+ private var _infoMessages = synchronizedMutableListOf<String>()
+ public val infoMessages: List<String> get() = _infoMessages.toList()
+
+ private var _progressMessages = synchronizedMutableListOf<String>()
+ public val progressMessages: List<String> get() = _progressMessages.toList()
+
+ private var _warnMessages = synchronizedMutableListOf<String>()
+ public val warnMessages: List<String> get() = _warnMessages.toList()
+
+ private var _errorMessages = synchronizedMutableListOf<String>()
+ public val errorMessages: List<String> get() = _errorMessages.toList()
+
+ override fun debug(message: String) {
+ _debugMessages.add(message)
+ logger.debug(message)
+ }
+
+ override fun info(message: String) {
+ _infoMessages.add(message)
+ logger.info(message)
+ }
+
+ override fun progress(message: String) {
+ _progressMessages.add(message)
+ logger.progress(message)
+ }
+
+ override fun warn(message: String) {
+ _warnMessages.add(message)
+ logger.warn(message)
+ }
+
+ override fun error(message: String) {
+ _errorMessages.add(message)
+ logger.error(message)
+ }
+
+ private fun <T> synchronizedMutableListOf(): MutableList<T> = Collections.synchronizedList(mutableListOf())
+}
diff --git a/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestDokkaConfigurationBuilder.kt b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestDokkaConfigurationBuilder.kt
new file mode 100644
index 00000000..4c451fed
--- /dev/null
+++ b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestDokkaConfigurationBuilder.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package testApi.testRunner
+
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.CustomDocTag
+import org.jetbrains.dokka.model.doc.Description
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.model.doc.Text
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import java.io.File
+
+public fun dokkaConfiguration(block: TestDokkaConfigurationBuilder.() -> Unit): DokkaConfigurationImpl =
+ TestDokkaConfigurationBuilder().apply(block).build()
+
+@DslMarker
+public annotation class DokkaConfigurationDsl
+
+// TODO this class heavily relies on `DokkaSourceSetImpl`, should be refactored to `DokkaSourceSet`
+@DokkaConfigurationDsl
+public class TestDokkaConfigurationBuilder {
+
+ public var moduleName: String = "root"
+ set(value) {
+ check(lazySourceSets.isEmpty()) { "Cannot set moduleName after adding source sets" }
+ field = value
+ }
+ public var moduleVersion: String = "1.0-SNAPSHOT"
+ public var outputDir: File = File("out")
+ public var format: String = "html"
+ public var offlineMode: Boolean = false
+ public var cacheRoot: String? = null
+ public var pluginsClasspath: List<File> = emptyList()
+ public var pluginsConfigurations: MutableList<PluginConfigurationImpl> = mutableListOf()
+ public var failOnWarning: Boolean = false
+ public var modules: List<DokkaModuleDescriptionImpl> = emptyList()
+ public var suppressObviousFunctions: Boolean = DokkaDefaults.suppressObviousFunctions
+ public var includes: List<File> = emptyList()
+ public var suppressInheritedMembers: Boolean = DokkaDefaults.suppressInheritedMembers
+ public var delayTemplateSubstitution: Boolean = DokkaDefaults.delayTemplateSubstitution
+ private val lazySourceSets = mutableListOf<Lazy<DokkaSourceSetImpl>>()
+
+ public fun build(): DokkaConfigurationImpl = DokkaConfigurationImpl(
+ moduleName = moduleName,
+ moduleVersion = moduleVersion,
+ outputDir = outputDir,
+ cacheRoot = cacheRoot?.let(::File),
+ offlineMode = offlineMode,
+ sourceSets = lazySourceSets.map { it.value }.toList(),
+ pluginsClasspath = pluginsClasspath,
+ pluginsConfiguration = pluginsConfigurations,
+ modules = modules,
+ failOnWarning = failOnWarning,
+ suppressObviousFunctions = suppressObviousFunctions,
+ includes = includes.toSet(),
+ suppressInheritedMembers = suppressInheritedMembers,
+ delayTemplateSubstitution = delayTemplateSubstitution,
+ finalizeCoroutines = false
+ )
+
+ public fun sourceSets(block: SourceSetsBuilder.() -> Unit) {
+ lazySourceSets.addAll(SourceSetsBuilder(moduleName).apply(block))
+ }
+
+ public fun sourceSet(block: DokkaSourceSetBuilder.() -> Unit): Lazy<DokkaSourceSetImpl> {
+ val lazySourceSet = lazy { DokkaSourceSetBuilder(moduleName).apply(block).build() }
+ lazySourceSets.add(lazySourceSet)
+ return lazySourceSet
+ }
+
+ public fun unattachedSourceSet(block: DokkaSourceSetBuilder.() -> Unit): DokkaSourceSetImpl {
+ return DokkaSourceSetBuilder(moduleName).apply(block).build()
+ }
+}
+
+@DokkaConfigurationDsl
+public class SourceSetsBuilder(
+ public val moduleName: String
+) : ArrayList<Lazy<DokkaSourceSetImpl>>() {
+ public fun sourceSet(block: DokkaSourceSetBuilder.() -> Unit): Lazy<DokkaConfiguration.DokkaSourceSet> {
+ return lazy { DokkaSourceSetBuilder(moduleName).apply(block).build() }.apply(::add)
+ }
+}
+
+@DokkaConfigurationDsl
+public class DokkaSourceSetBuilder(
+ private val moduleName: String,
+ public var name: String = "main",
+ public var displayName: String = "JVM",
+ public var classpath: List<String> = emptyList(),
+ public var sourceRoots: List<String> = emptyList(),
+ public var dependentSourceSets: Set<DokkaSourceSetID> = emptySet(),
+ public var samples: List<String> = emptyList(),
+ public var includes: List<String> = emptyList(),
+ @Deprecated(message = "Use [documentedVisibilities] property for a more flexible control over documented visibilities")
+ public var includeNonPublic: Boolean = false,
+ public var documentedVisibilities: Set<DokkaConfiguration.Visibility> = DokkaDefaults.documentedVisibilities,
+ public var reportUndocumented: Boolean = false,
+ public var skipEmptyPackages: Boolean = false,
+ public var skipDeprecated: Boolean = false,
+ public var jdkVersion: Int = 8,
+ public var languageVersion: String? = null,
+ public var apiVersion: String? = null,
+ public var noStdlibLink: Boolean = false,
+ public var noJdkLink: Boolean = false,
+ public var suppressedFiles: List<String> = emptyList(),
+ public var analysisPlatform: String = "jvm",
+ public var perPackageOptions: List<PackageOptionsImpl> = emptyList(),
+ public var externalDocumentationLinks: List<ExternalDocumentationLinkImpl> = emptyList(),
+ public var sourceLinks: List<SourceLinkDefinitionImpl> = emptyList()
+) {
+ @Suppress("DEPRECATION")
+ public fun build(): DokkaSourceSetImpl {
+ return DokkaSourceSetImpl(
+ displayName = displayName,
+ sourceSetID = DokkaSourceSetID(moduleName, name),
+ classpath = classpath.map(::File),
+ sourceRoots = sourceRoots.map(::File).toSet(),
+ dependentSourceSets = dependentSourceSets,
+ samples = samples.map(::File).toSet(),
+ includes = includes.map(::File).toSet(),
+ includeNonPublic = includeNonPublic,
+ documentedVisibilities = documentedVisibilities,
+ reportUndocumented = reportUndocumented,
+ skipEmptyPackages = skipEmptyPackages,
+ skipDeprecated = skipDeprecated,
+ jdkVersion = jdkVersion,
+ sourceLinks = sourceLinks.toSet(),
+ perPackageOptions = perPackageOptions.toList(),
+ externalDocumentationLinks = externalDocumentationLinks.toSet(),
+ languageVersion = languageVersion,
+ apiVersion = apiVersion,
+ noStdlibLink = noStdlibLink,
+ noJdkLink = noJdkLink,
+ suppressedFiles = suppressedFiles.map(::File).toSet(),
+ analysisPlatform = Platform.fromString(analysisPlatform)
+ )
+ }
+}
+
+public val defaultSourceSet: DokkaSourceSetImpl = DokkaSourceSetImpl(
+ displayName = "DEFAULT",
+ sourceSetID = DokkaSourceSetID("DEFAULT", "DEFAULT"),
+ classpath = emptyList(),
+ sourceRoots = emptySet(),
+ dependentSourceSets = emptySet(),
+ samples = emptySet(),
+ includes = emptySet(),
+ includeNonPublic = false,
+ documentedVisibilities = DokkaDefaults.documentedVisibilities,
+ reportUndocumented = false,
+ skipEmptyPackages = true,
+ skipDeprecated = false,
+ jdkVersion = 8,
+ sourceLinks = emptySet(),
+ perPackageOptions = emptyList(),
+ externalDocumentationLinks = emptySet(),
+ languageVersion = null,
+ apiVersion = null,
+ noStdlibLink = false,
+ noJdkLink = false,
+ suppressedFiles = emptySet(),
+ analysisPlatform = Platform.DEFAULT
+)
+
+public fun sourceSet(name: String): DokkaConfiguration.DokkaSourceSet {
+ return defaultSourceSet.copy(
+ displayName = name,
+ sourceSetID = defaultSourceSet.sourceSetID.copy(sourceSetName = name)
+ )
+}
+
+public fun dModule(
+ name: String,
+ packages: List<DPackage> = emptyList(),
+ documentation: SourceSetDependent<DocumentationNode> = emptyMap(),
+ expectPresentInSet: DokkaConfiguration.DokkaSourceSet? = null,
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet> = emptySet(),
+ extra: PropertyContainer<DModule> = PropertyContainer.empty()
+): DModule = DModule(
+ name = name,
+ packages = packages,
+ documentation = documentation,
+ expectPresentInSet = expectPresentInSet,
+ sourceSets = sourceSets,
+ extra = extra
+)
+
+public fun dPackage(
+ dri: DRI,
+ functions: List<DFunction> = emptyList(),
+ properties: List<DProperty> = emptyList(),
+ classlikes: List<DClasslike> = emptyList(),
+ typealiases: List<DTypeAlias> = emptyList(),
+ documentation: SourceSetDependent<DocumentationNode> = emptyMap(),
+ expectPresentInSet: DokkaConfiguration.DokkaSourceSet? = null,
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet> = emptySet(),
+ extra: PropertyContainer<DPackage> = PropertyContainer.empty()
+): DPackage = DPackage(
+ dri = dri,
+ functions = functions,
+ properties = properties,
+ classlikes = classlikes,
+ typealiases = typealiases,
+ documentation = documentation,
+ expectPresentInSet = expectPresentInSet,
+ sourceSets = sourceSets,
+ extra = extra
+)
+
+public fun documentationNode(vararg texts: String): DocumentationNode {
+ return DocumentationNode(
+ texts.toList()
+ .map { Description(CustomDocTag(listOf(Text(it)), name = "MARKDOWN_FILE")) })
+}
diff --git a/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestRunner.kt b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestRunner.kt
new file mode 100644
index 00000000..1f7ee060
--- /dev/null
+++ b/dokka-subprojects/core-test-api/src/main/kotlin/org/jetbrains/dokka/testApi/testRunner/TestRunner.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.testApi.testRunner
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.ExternalDocumentationLinkImpl
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.testApi.logger.TestLogger
+import org.jetbrains.dokka.utilities.DokkaLogger
+import testApi.testRunner.TestDokkaConfigurationBuilder
+import java.io.File
+import java.net.URL
+import java.nio.charset.Charset
+import java.nio.file.Files
+import java.nio.file.InvalidPathException
+import java.nio.file.Path
+import java.nio.file.Paths
+
+// TODO: take dokka configuration from file
+public abstract class AbstractTest<M : TestMethods, T : TestBuilder<M>, D : DokkaTestGenerator<M>>(
+ protected val testBuilder: () -> T,
+ protected val dokkaTestGenerator: (DokkaConfiguration, DokkaLogger, M, List<DokkaPlugin>) -> D,
+ protected val logger: TestLogger,
+) {
+ protected fun getTestDataDir(name: String): Path {
+ return File("src/test/resources/$name").takeIf { it.exists() }?.toPath()
+ ?: throw InvalidPathException(name, "Cannot be found")
+ }
+
+ /**
+ * @param cleanupOutput if set to true, any temporary files will be cleaned up after execution. If set to false,
+ * it will be left to the user or the OS to delete it. Has no effect if [useOutputLocationFromConfig]
+ * is also set to true.
+ * @param useOutputLocationFromConfig if set to true, output location specified in [DokkaConfigurationImpl.outputDir]
+ * will be used. If set to false, a temporary folder will be used instead.
+ */
+ protected fun testFromData(
+ configuration: DokkaConfigurationImpl,
+ cleanupOutput: Boolean = true,
+ useOutputLocationFromConfig: Boolean = false,
+ pluginOverrides: List<DokkaPlugin> = emptyList(),
+ block: T.() -> Unit,
+ ) {
+ if (useOutputLocationFromConfig) {
+ runTests(
+ configuration = configuration,
+ pluginOverrides = pluginOverrides,
+ testLogger = logger,
+ block = block
+ )
+ } else {
+ withTempDirectory(cleanUpAfterUse = cleanupOutput) { tempDir ->
+ if (!cleanupOutput) {
+ logger.info("Output will be generated under: ${tempDir.absolutePath}")
+ }
+ runTests(
+ configuration = configuration.copy(outputDir = tempDir),
+ pluginOverrides = pluginOverrides,
+ testLogger = logger,
+ block = block
+ )
+ }
+ }
+ }
+
+ protected fun testInline(
+ query: String,
+ configuration: DokkaConfigurationImpl,
+ cleanupOutput: Boolean = true,
+ pluginOverrides: List<DokkaPlugin> = emptyList(),
+ loggerForTest: DokkaLogger = logger,
+ block: T.() -> Unit,
+ ) {
+ withTempDirectory(cleanUpAfterUse = cleanupOutput) { tempDir ->
+ if (!cleanupOutput) {
+ loggerForTest.info("Output will be generated under: ${tempDir.absolutePath}")
+ }
+
+ val fileMap = query.toFileMap()
+ fileMap.materializeFiles(tempDir.toPath().toAbsolutePath())
+
+ val newConfiguration = configuration.copy(
+ outputDir = tempDir,
+ sourceSets = configuration.sourceSets.map { sourceSet ->
+ sourceSet.copy(
+ sourceRoots = sourceSet.sourceRoots.map { file -> tempDir.resolve(file) }.toSet(),
+ suppressedFiles = sourceSet.suppressedFiles.map { file -> tempDir.resolve(file) }.toSet(),
+ sourceLinks = sourceSet.sourceLinks.map {
+ link -> link.copy(localDirectory = tempDir.resolve(link.localDirectory).absolutePath)
+ }.toSet(),
+ includes = sourceSet.includes.map { file -> tempDir.resolve(file) }.toSet()
+ )
+ }
+ )
+ runTests(
+ configuration = newConfiguration,
+ pluginOverrides = pluginOverrides,
+ testLogger = loggerForTest,
+ block = block
+ )
+ }
+ }
+
+ private fun withTempDirectory(cleanUpAfterUse: Boolean, block: (tempDirectory: File) -> Unit) {
+ val tempDir = this.createTempDir()
+ try {
+ block(tempDir)
+ } finally {
+ if (cleanUpAfterUse) {
+ tempDir.deleteRecursively()
+ }
+ }
+ }
+
+ private fun runTests(
+ configuration: DokkaConfiguration,
+ pluginOverrides: List<DokkaPlugin>,
+ testLogger: DokkaLogger = logger,
+ block: T.() -> Unit
+ ) {
+ val testMethods = testBuilder().apply(block).build()
+ dokkaTestGenerator(
+ configuration,
+ testLogger,
+ testMethods,
+ pluginOverrides
+ ).generate()
+ }
+
+ private fun String.toFileMap(): Map<String, String> {
+ return this.trimIndent().trimMargin()
+ .replace("\r\n", "\n")
+ .sliceAt(filePathRegex)
+ .filter { it.isNotEmpty() && it.isNotBlank() && "\n" in it }
+ .map { fileDeclaration -> fileDeclaration.trim() }.associate { fileDeclaration ->
+ val filePathAndContent = fileDeclaration.split("\n", limit = 2)
+ val filePath = filePathAndContent.first().removePrefix("/").trim()
+ val content = filePathAndContent.last().trim()
+ filePath to content
+ }
+ }
+
+ private fun String.sliceAt(regex: Regex): List<String> {
+ val matchesStartIndices = regex.findAll(this).toList().map { match -> match.range.first }
+ return sequence {
+ yield(0)
+ yieldAll(matchesStartIndices)
+ yield(this@sliceAt.length)
+ }
+ .zipWithNext { startIndex: Int, endIndex: Int -> substring(startIndex, endIndex) }
+ .toList()
+ .also { slices ->
+ /* Post-condition verifying that no character is lost */
+ check(slices.sumBy { it.length } == length)
+ }
+ }
+
+ private fun Map<String, String>.materializeFiles(
+ root: Path = Paths.get("."),
+ charset: Charset = Charset.forName("utf-8"),
+ ) = this.map { (path, content) ->
+ val file = root.resolve(path)
+ Files.createDirectories(file.parent)
+ Files.write(file, content.toByteArray(charset))
+ }
+
+ @Suppress("DEPRECATION") // TODO migrate to kotlin.io.path.createTempDirectory with languageVersion >= 1.5
+ private fun createTempDir(): File = kotlin.io.createTempDir()
+
+ protected fun dokkaConfiguration(block: TestDokkaConfigurationBuilder.() -> Unit): DokkaConfigurationImpl =
+ testApi.testRunner.dokkaConfiguration(block)
+
+
+ protected val jvmStdlibPath: String? by lazy {
+ ClassLoader.getSystemResource("kotlin/jvm/Strictfp.class")
+ ?.file
+ ?.replace("file:", "")
+ ?.replaceAfter(".jar", "")
+ }
+
+ protected val jsStdlibPath: String? by lazy {
+ ClassLoader.getSystemResource("kotlin/jquery")
+ ?.file
+ ?.replace("file:", "")
+ ?.replaceAfter(".jar", "")
+ }
+
+ protected val commonStdlibPath: String? by lazy {
+ // `kotlin-stdlib-common` is legacy
+ // we can use any platform dependency
+ // since common code should be resolved with all platform
+ jvmStdlibPath
+ }
+
+ protected val stdlibExternalDocumentationLink: ExternalDocumentationLinkImpl = ExternalDocumentationLinkImpl(
+ URL("https://kotlinlang.org/api/latest/jvm/stdlib/"),
+ URL("https://kotlinlang.org/api/latest/jvm/stdlib/package-list")
+ )
+
+ public companion object {
+ private val filePathRegex = Regex("""[\n^](\/[\w|\-]+)+(\.\w+)?\s*\n""")
+ }
+}
+
+public interface TestMethods
+
+public open class CoreTestMethods(
+ public open val pluginsSetupStage: (DokkaContext) -> Unit,
+ public open val verificationStage: (() -> Unit) -> Unit,
+ public open val documentablesCreationStage: (List<DModule>) -> Unit,
+ public open val documentablesMergingStage: (DModule) -> Unit,
+ public open val documentablesTransformationStage: (DModule) -> Unit,
+ public open val pagesGenerationStage: (RootPageNode) -> Unit,
+ public open val pagesTransformationStage: (RootPageNode) -> Unit,
+ public open val renderingStage: (RootPageNode, DokkaContext) -> Unit,
+) : TestMethods
+
+public abstract class TestBuilder<M : TestMethods> {
+ public abstract fun build(): M
+}
+
+public abstract class DokkaTestGenerator<T : TestMethods>(
+ protected val configuration: DokkaConfiguration,
+ protected val logger: DokkaLogger,
+ protected val testMethods: T,
+ protected val additionalPlugins: List<DokkaPlugin> = emptyList(),
+) {
+ public abstract fun generate()
+}