All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.dokka.testApi.testRunner.TestRunner.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2024 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, D : DokkaTestGenerator>(
    protected val testBuilder: () -> T,
    protected val dokkaTestGenerator: (DokkaConfiguration, DokkaLogger, M, List) -> 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 = 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 = 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,
        testLogger: DokkaLogger = logger,
        block: T.() -> Unit
    ) {
        val testMethods = testBuilder().apply(block).build()
        dokkaTestGenerator(
            configuration,
            testLogger,
            testMethods,
            pluginOverrides
        ).generate()
    }

    private fun String.toFileMap(): Map {
        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 {
        val matchesStartIndices = regex.findAll(this).toList().map { match -> match.range.first }
        return sequence {
            yield(0)
            yieldAll(matchesStartIndices)
            yield([email protected])
        }
            .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.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/core/"),
        URL("https://kotlinlang.org/api/core/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) -> 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 {
    public abstract fun build(): M
}

public abstract class DokkaTestGenerator(
    protected val configuration: DokkaConfiguration,
    protected val logger: DokkaLogger,
    protected val testMethods: T,
    protected val additionalPlugins: List = emptyList(),
) {
    public abstract fun generate()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy