package org.jetbrains.dokka.tests import com.google.inject.Guice import com.intellij.openapi.application.PathManager import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.io.FileUtil import org.jetbrains.dokka.* import org.jetbrains.dokka.Utilities.DokkaModule import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot import org.jetbrains.kotlin.config.ContentRoot import org.jetbrains.kotlin.config.KotlinSourceRoot import org.junit.Assert import org.junit.Assert.fail import java.io.File fun verifyModel(vararg roots: ContentRoot, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, format: String = "html", includeNonPublic: Boolean = true, verifier: (DocumentationModule) -> Unit) { val messageCollector = object : MessageCollector { override fun clear() { } override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { when (severity) { CompilerMessageSeverity.WARNING, CompilerMessageSeverity.LOGGING, CompilerMessageSeverity.OUTPUT, CompilerMessageSeverity.INFO, CompilerMessageSeverity.ERROR -> { println("$severity: $message at $location") } CompilerMessageSeverity.EXCEPTION -> { fail("$severity: $message at $location") } } } override fun hasErrors() = false } val environment = AnalysisEnvironment(messageCollector) environment.apply { if (withJdk || withKotlinRuntime) { val stringRoot = PathManager.getResourceRoot(String::class.java, "/java/lang/String.class") addClasspath(File(stringRoot)) } if (withKotlinRuntime) { val kotlinPairRoot = PathManager.getResourceRoot(Pair::class.java, "/kotlin/Pair.class") addClasspath(File(kotlinPairRoot)) val kotlinStrictfpRoot = PathManager.getResourceRoot(Strictfp::class.java, "/kotlin/jvm/Strictfp.class") addClasspath(File(kotlinStrictfpRoot)) } addRoots(roots.toList()) } val options = DocumentationOptions("", format, includeNonPublic = includeNonPublic, skipEmptyPackages = false, sourceLinks = listOf<SourceLinkDefinition>(), generateIndexPages = false) val injector = Guice.createInjector(DokkaModule(environment, options, DokkaConsoleLogger)) val documentation = buildDocumentationModule(injector, "test") verifier(documentation) Disposer.dispose(environment) } fun verifyModel(source: String, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, format: String = "html", includeNonPublic: Boolean = true, verifier: (DocumentationModule) -> Unit) { if (!File(source).exists()) { throw IllegalArgumentException("Can't find test data file $source") } verifyModel(contentRootFromPath(source), withJdk = withJdk, withKotlinRuntime = withKotlinRuntime, format = format, includeNonPublic = includeNonPublic, verifier = verifier) } fun verifyPackageMember(source: String, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, verifier: (DocumentationNode) -> Unit) { verifyModel(source, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) { model -> val pkg = model.members.single() verifier(pkg.members.single()) } } fun verifyJavaModel(source: String, withKotlinRuntime: Boolean = false, verifier: (DocumentationModule) -> Unit) { val tempDir = FileUtil.createTempDirectory("dokka", "") try { val sourceFile = File(source) FileUtil.copy(sourceFile, File(tempDir, sourceFile.name)) verifyModel(JavaSourceRoot(tempDir, null), withJdk = true, withKotlinRuntime = withKotlinRuntime, verifier = verifier) } finally { FileUtil.delete(tempDir) } } fun verifyJavaPackageMember(source: String, withKotlinRuntime: Boolean = false, verifier: (DocumentationNode) -> Unit) { verifyJavaModel(source, withKotlinRuntime) { model -> val pkg = model.members.single() verifier(pkg.members.single()) } } fun verifyOutput(roots: Array<ContentRoot>, outputExtension: String, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { verifyModel(*roots, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) { verifyModelOutput(it, outputExtension, outputGenerator, roots.first().path) } } private fun verifyModelOutput(it: DocumentationModule, outputExtension: String, outputGenerator: (DocumentationModule, StringBuilder) -> Unit, sourcePath: String) { val output = StringBuilder() outputGenerator(it, output) val ext = outputExtension.removePrefix(".") val path = sourcePath val expectedOutput = File(path.replaceAfterLast(".", ext, path + "." + ext)).readText() assertEqualsIgnoringSeparators(expectedOutput, output.toString()) } fun verifyOutput(path: String, outputExtension: String, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { verifyOutput(arrayOf(contentRootFromPath(path)), outputExtension, withJdk, withKotlinRuntime, outputGenerator) } fun verifyJavaOutput(path: String, outputExtension: String, withKotlinRuntime: Boolean = false, outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { verifyJavaModel(path, withKotlinRuntime) { model -> verifyModelOutput(model, outputExtension, outputGenerator, path) } } fun assertEqualsIgnoringSeparators(expectedOutput: String, output: String) { Assert.assertEquals(expectedOutput.replace("\r\n", "\n"), output.replace("\r\n", "\n")) } fun StringBuilder.appendChildren(node: ContentBlock): StringBuilder { for (child in node.children) { val childText = child.toTestString() append(childText) } return this } fun StringBuilder.appendNode(node: ContentNode): StringBuilder { when (node) { is ContentText -> { append(node.text) } is ContentEmphasis -> append("*").appendChildren(node).append("*") is ContentBlockCode -> { if (node.language.isNotBlank()) appendln("[code lang=${node.language}]") else appendln("[code]") appendChildren(node) appendln() appendln("[/code]") } is ContentNodeLink -> { append("[") appendChildren(node) append(" -> ") append(node.node.toString()) append("]") } is ContentBlock -> { appendChildren(node) } is ContentEmpty -> { /* nothing */ } else -> throw IllegalStateException("Don't know how to format node $node") } return this } fun ContentNode.toTestString(): String { val node = this return StringBuilder().apply { appendNode(node) }.toString() } class InMemoryLocation(override val path: String): Location { override fun relativePathTo(other: Location, anchor: String?): String = if (anchor != null) other.path + "#" + anchor else other.path } object InMemoryLocationService: LocationService { override fun location(qualifiedName: List<String>, hasMembers: Boolean) = InMemoryLocation(relativePathToNode(qualifiedName, hasMembers)) override val root: Location get() = InMemoryLocation("") } val tempLocation = InMemoryLocation("") val ContentRoot.path: String get() = when(this) { is KotlinSourceRoot -> path is JavaSourceRoot -> file.path else -> throw UnsupportedOperationException() }