aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaweł Marks <pmarks@virtuslab.com>2020-03-25 16:11:21 +0100
committerPaweł Marks <Kordyjan@users.noreply.github.com>2020-03-31 16:02:09 +0200
commit136a835671ef0cdf09c3475db1b61e15aee1be48 (patch)
tree5884860a75c1f835cf6139539a7625ade7d95e49
parenta8e5b94f8e95138dbfa913b74a4d2d65fd5993c1 (diff)
downloaddokka-136a835671ef0cdf09c3475db1b61e15aee1be48.tar.gz
dokka-136a835671ef0cdf09c3475db1b61e15aee1be48.tar.bz2
dokka-136a835671ef0cdf09c3475db1b61e15aee1be48.zip
Add test-tools module and matchers for content inside of it
-rw-r--r--.idea/compiler.xml2
-rw-r--r--settings.gradle.kts3
-rw-r--r--test-tools/build.gradle.kts4
-rw-r--r--test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt61
-rw-r--r--test-tools/src/main/kotlin/matchers/content/contentMatchers.kt105
5 files changed, 174 insertions, 1 deletions
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 0848aa0c..924f38f8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -66,6 +66,8 @@
<module name="dokka.runners.maven-plugin.main" target="1.8" />
<module name="dokka.runners.maven-plugin.test" target="1.8" />
<module name="dokka.runners.test" target="1.8" />
+ <module name="dokka.test-tools.main" target="1.8" />
+ <module name="dokka.test-tools.test" target="1.8" />
<module name="dokka.testApi.main" target="1.8" />
<module name="dokka.testApi.test" target="1.8" />
<module name="fatjar_main" target="1.8" />
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 263a1b09..8b4d4050 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,6 +3,7 @@ rootProject.name = "dokka"
include("core")
include("coreDependencies")
include("testApi")
+include("test-tools")
include("runners:gradle-plugin")
include("runners:cli")
include("runners:maven-plugin")
@@ -26,4 +27,4 @@ pluginManagement {
jcenter()
gradlePluginPortal()
}
-} \ No newline at end of file
+}
diff --git a/test-tools/build.gradle.kts b/test-tools/build.gradle.kts
new file mode 100644
index 00000000..4f6d2500
--- /dev/null
+++ b/test-tools/build.gradle.kts
@@ -0,0 +1,4 @@
+dependencies {
+ implementation(project(":testApi"))
+ implementation(kotlin("stdlib-jdk8"))
+} \ No newline at end of file
diff --git a/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt b/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt
new file mode 100644
index 00000000..ae3ecab7
--- /dev/null
+++ b/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt
@@ -0,0 +1,61 @@
+package matchers.content
+
+import org.jetbrains.dokka.pages.ContentComposite
+import org.jetbrains.dokka.pages.ContentGroup
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.test.tools.matchers.content.*
+import kotlin.reflect.KClass
+
+// entry point:
+fun ContentNode.assertNode(block: ContentMatcherBuilder<ContentComposite>.() -> Unit) {
+ ContentMatcherBuilder(ContentComposite::class).apply(block).build().tryMatch(this)
+}
+
+
+// DSL:
+@DslMarker
+annotation class ContentMatchersDsl
+
+@ContentMatchersDsl
+class ContentMatcherBuilder<T : ContentComposite> @PublishedApi internal constructor(private val kclass: KClass<T>) {
+ @PublishedApi
+ internal val children = mutableListOf<MatcherElement>()
+ internal val assertions = mutableListOf<T.() -> Unit>()
+
+ fun build() = CompositeMatcher(kclass, children) { assertions.forEach { it() } }
+
+ // part of DSL that cannot be defined as an extension
+ operator fun String.unaryPlus() {
+ children += TextMatcher(this)
+ }
+}
+
+fun <T : ContentComposite> ContentMatcherBuilder<T>.check(assertion: T.() -> Unit) {
+ assertions += assertion
+}
+
+inline fun <reified S : ContentComposite> ContentMatcherBuilder<*>.composite(
+ block: ContentMatcherBuilder<S>.() -> Unit
+) {
+ children += ContentMatcherBuilder(S::class).apply(block).build()
+}
+
+inline fun <reified S : ContentNode> ContentMatcherBuilder<*>.node(noinline assertions: S.() -> Unit) {
+ children += NodeMatcher(S::class, assertions)
+}
+
+fun ContentMatcherBuilder<*>.skipAllNotMatching() {
+ children += Anything
+}
+
+
+// Convenience functions:
+fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) {
+ children += ContentMatcherBuilder(ContentGroup::class).apply(block).build()
+}
+
+fun ContentMatcherBuilder<*>.somewhere(block: ContentMatcherBuilder<*>.() -> Unit) {
+ skipAllNotMatching()
+ block()
+ skipAllNotMatching()
+} \ No newline at end of file
diff --git a/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt b/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt
new file mode 100644
index 00000000..c4400646
--- /dev/null
+++ b/test-tools/src/main/kotlin/matchers/content/contentMatchers.kt
@@ -0,0 +1,105 @@
+package org.jetbrains.dokka.test.tools.matchers.content
+
+import org.jetbrains.dokka.pages.ContentComposite
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.pages.ContentText
+import kotlin.reflect.KClass
+import kotlin.reflect.full.cast
+
+sealed class MatcherElement
+
+class TextMatcher(val text: String) : MatcherElement()
+
+open class NodeMatcher<T: ContentNode>(
+ val kclass: KClass<T>,
+ val assertions: T.() -> Unit = {}
+): MatcherElement() {
+ open fun tryMatch(node: ContentNode) {
+ assertions(kclass.cast(node))
+ }
+}
+
+class CompositeMatcher<T : ContentComposite>(
+ kclass: KClass<T>,
+ private val children: List<MatcherElement>,
+ assertions: T.() -> Unit = {}
+) : NodeMatcher<T>(kclass, assertions) {
+ private val normalizedChildren: List<MatcherElement> by lazy {
+ children.fold(listOf<MatcherElement>()) { acc, e ->
+ when {
+ acc.lastOrNull() is Anything && e is Anything -> acc
+ acc.lastOrNull() is TextMatcher && e is TextMatcher ->
+ acc.dropLast(1) + TextMatcher((acc.lastOrNull() as TextMatcher).text + e.text)
+ else -> acc + e
+ }
+ }
+ }
+
+ override fun tryMatch(node: ContentNode) {
+ super.tryMatch(node)
+ kclass.cast(node).children.fold(normalizedChildren.pop()) { acc, n -> acc.next(n) }.finish()
+ }
+}
+
+object Anything : MatcherElement()
+
+private sealed class MatchWalkerState {
+ abstract fun next(node: ContentNode): MatchWalkerState
+ abstract fun finish()
+}
+
+private class TextMatcherState(val text: String, val rest: List<MatcherElement>) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState {
+ node as ContentText
+ return when {
+ text == node.text -> rest.pop()
+ text.startsWith(node.text) -> TextMatcherState(text.removePrefix(node.text), rest)
+ else -> throw AssertionError("Expected text: \"$text\", but found: \"${node.text}\"")
+ }
+ }
+
+ override fun finish() = throw AssertionError("\"$text\" was not found" + rest.messageEnd)
+}
+
+private object EmptyMatcherState : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState {
+ throw AssertionError("Unexpected $node")
+ }
+
+ override fun finish() = Unit
+}
+
+private class NodeMatcherState(
+ val matcher: NodeMatcher<*>,
+ val rest: List<MatcherElement>
+) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState {
+ matcher.tryMatch(node)
+ return rest.pop()
+ }
+
+ override fun finish() = throw AssertionError("Composite of type ${matcher.kclass} was not found" + rest.messageEnd)
+}
+
+private class SkippingMatcherState(
+ val innerState: MatchWalkerState
+) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState = runCatching { innerState.next(node) }.getOrElse { this }
+
+ override fun finish() = innerState.finish()
+}
+
+private fun List<MatcherElement>.pop(): MatchWalkerState = when (val head = firstOrNull()) {
+ is TextMatcher -> TextMatcherState(head.text, drop(1))
+ is NodeMatcher<*> -> NodeMatcherState(head, drop(1))
+ is Anything -> SkippingMatcherState(drop(1).pop())
+ null -> EmptyMatcherState
+}
+
+private val List<MatcherElement>.messageEnd: String
+ get() = filter { it !is Anything }
+ .count().takeIf { it > 0 }
+ ?.let { " and $it further matchers were not satisfied" } ?: ""
+
+// Creating this whole mechanism was most scala-like experience I had since I stopped using scala.
+// I don't know how I should feel about it. \ No newline at end of file