package org.jetbrains.kmark.test import org.junit.runner.* import org.junit.runner.notification.* import java.io.File import org.junit.runners.ParentRunner import java.io.Serializable import kotlin.properties.Delegates import org.junit.ComparisonFailure data class MarkdownTestUniqueId(val id: Int) : Serializable { class object { var id = 0 fun next() = MarkdownTestUniqueId(id++) } } public open class MarkdownSpecification(val path: String, val processor: (String) -> String) trait MarkdownTest { fun description(): Description } public open class MarkdownTestCase(val spec: MarkdownSpecification, val input: String, val expected: String) : MarkdownTest, Runner() { val _description by Delegates.lazy { Description.createSuiteDescription(input, MarkdownTestUniqueId.next())!! } override fun description(): Description = _description override fun getDescription(): Description? = description() override fun run(notifier: RunNotifier?) { notifier!! notifier.fireTestStarted(_description) val result = spec.processor(input) when (result) { expected -> notifier.fireTestFinished(_description) else -> notifier.fireTestFailure(Failure(_description, ComparisonFailure("Output mismatch", expected, result))) } } } public open class MarkdownTestSection(val spec: MarkdownSpecification, val title: String) : MarkdownTest, ParentRunner(spec.javaClass) { val children = arrayListOf(); val _description by Delegates.lazy { val desc = Description.createSuiteDescription(title, MarkdownTestUniqueId.next())!! for (item in getChildren()!!) { desc.addChild(describeChild(item)) } desc } override fun description(): Description = _description override fun getChildren(): MutableList? = children override fun describeChild(child: MarkdownTest?): Description? = child!!.description() override fun runChild(child: MarkdownTest?, notifier: RunNotifier?) { notifier!! when (child) { is MarkdownTestCase -> child.run(notifier) is MarkdownTestSection -> { if (child.children.size == 0) { notifier.fireTestStarted(child.description()) notifier.fireTestFinished(child.description()) } else { child.run(notifier) } } } } } public class MarkdownTestRunner(specificationClass: Class) : MarkdownTestSection(specificationClass.newInstance(), "Tests") { { val lines = File(spec.path).readLines() createSections(this, lines, 1) } private fun createTests(parent: MarkdownTestSection, lines: List): Int { val testMark = lines.takeWhile { it.trim() != "." } val testHtml = lines.drop(testMark.size).drop(1).takeWhile { it.trim() != "." } val markdown = testMark.join("\n", postfix = "\n", prefix = "\n") val html = testHtml.join("\n", postfix = "\n") val markdownTestCase = MarkdownTestCase(spec, markdown, html) parent.children.add(markdownTestCase) return testMark.size + testHtml.size + 3 } private fun createSections(parent: MarkdownTestSection, lines: List, level: Int): Int { var sectionNumber = 1 var index = 0 while (index < lines.size) { val line = lines[index] if (line.trim() == ".") { index = createTests(parent, lines.subList(index + 1, lines.lastIndex)) + index + 1 continue } val head = line.takeWhile { it == '#' }.length if (head == 0) { index++ continue } if (head < level) { return index } if (head == level) { val title = lines[index].dropWhile { it == '#' }.dropWhile { it.isWhitespace() } sectionNumber++ val section = MarkdownTestSection(spec, title) val lastIndex = createSections(section, lines.subList(index + 1, lines.lastIndex), level + 1) + index + 1 if (section.children.size > 0) parent.children.add(section) val nextHead = lines[lastIndex].takeWhile { it == '#' }.length if (nextHead < level) { return lastIndex } index = lastIndex continue } index++ } return lines.size } }