From f4fb28ae00afc0ce36420f0a93649ddefe66b0b5 Mon Sep 17 00:00:00 2001 From: Stefan Ludwig Date: Sat, 25 Aug 2018 19:05:58 +0200 Subject: [PATCH 1/4] refactoring to lay the foundation for streaming the execution of a document --- .../engine/DecisionTableToFixtureMatcher.kt | 11 +- .../kotlin/org/livingdoc/engine/LivingDoc.kt | 111 ++++++++++++++---- .../engine/ScenarioToFixtureMatcher.kt | 14 +++ .../livingdoc/example/CalculatorDocument.kt | 8 +- 4 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 livingdoc-engine/src/main/kotlin/org/livingdoc/engine/ScenarioToFixtureMatcher.kt diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt index 5660b808..fa1a56db 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt @@ -3,17 +3,20 @@ package org.livingdoc.engine import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableFixtureModel import org.livingdoc.repositories.model.decisiontable.DecisionTable import org.livingdoc.repositories.model.decisiontable.Header -import kotlin.reflect.KClass /** * Default matcher to find the right fixture classes for a given list of tables. */ class DecisionTableToFixtureMatcher { + fun findMatchingFixture(decisionTable: DecisionTable, fixtures: List>): Class<*>? { + return matchTablesToFixtures(listOf(decisionTable), fixtures)[decisionTable] + } + /** * Finds the fitting fixture for the given decision tables. */ - fun matchTablesToFixtures(tables: List, fixtures: List>): Map> { + fun matchTablesToFixtures(tables: List, fixtures: List>): Map> { /* * - Start with a list of Tables and a list of Fixtures * - Create mapping Model -> Fixture (straightforward) @@ -37,8 +40,8 @@ class DecisionTableToFixtureMatcher { table to fixture }.toMap() - private fun mapModelsToFixtures(fixtureClasses: List>) = - fixtureClasses.map { it.java }.associateByTo(mutableMapOf()) { clazz -> + private fun mapModelsToFixtures(fixtureClasses: List>) = + fixtureClasses.associateByTo(mutableMapOf()) { clazz -> DecisionTableFixtureModel(clazz) } diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt index d981c755..60fc5445 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt @@ -2,13 +2,15 @@ package org.livingdoc.engine import org.livingdoc.api.documents.ExecutableDocument import org.livingdoc.api.fixtures.decisiontables.DecisionTableFixture +import org.livingdoc.api.fixtures.scenarios.ScenarioFixture import org.livingdoc.engine.execution.DocumentResult import org.livingdoc.engine.execution.ExecutionException import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableExecutor -import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableFixtureModel +import org.livingdoc.engine.execution.examples.scenarios.ScenarioExecutor +import org.livingdoc.repositories.Document import org.livingdoc.repositories.RepositoryManager import org.livingdoc.repositories.config.Configuration -import org.livingdoc.repositories.model.decisiontable.DecisionTable +import kotlin.reflect.KClass /** * Executes the given document class and returns the [DocumentResult]. The document's class must be annotated @@ -18,38 +20,101 @@ import org.livingdoc.repositories.model.decisiontable.DecisionTable * @throws ExecutionException in case the execution failed in a way that did not produce a viable result * @since 2.0 */ -class LivingDoc(val tableMatcher: DecisionTableToFixtureMatcher = DecisionTableToFixtureMatcher()) { +class LivingDoc( + val repositoryManager: RepositoryManager = RepositoryManager.from(Configuration.load()), + val decisionTableExecutor: DecisionTableExecutor = DecisionTableExecutor(), + val decisionTableToFixtureMatcher: DecisionTableToFixtureMatcher = DecisionTableToFixtureMatcher(), + val scenarioExecutor: ScenarioExecutor = ScenarioExecutor(), + val scenarioToFixtureMatcher: ScenarioToFixtureMatcher = ScenarioToFixtureMatcher() +) { - private fun validateInputAndMatchFixtures(document: Class<*>): Map> { - if (!document.isAnnotationPresent(ExecutableDocument::class.java)) { - throw IllegalArgumentException("ExecutableDocument annotation is not present on class ${document.canonicalName}.") - } + @Throws(ExecutionException::class) + fun execute(documentClass: Class<*>): DocumentResult { + val documentClassModel = ExecutableDocumentModel.of(documentClass) - val annotation = document.getAnnotation(ExecutableDocument::class.java) - val values = annotation.value.split("://") - if (values.size != 2) throw IllegalArgumentException("Illegal annotation value '${annotation.value}'.") + val document = loadDocument(documentClassModel) - val (name, id) = values - val repository = RepositoryManager.from(Configuration.load()).getRepository(name) - val (tables, _) = repository.getDocument(id) + val decisionTableFixtures = documentClassModel.decisionTableFixtures + val decisionTableResults = document.tables.mapNotNull { + val fixture = decisionTableToFixtureMatcher.findMatchingFixture(it, decisionTableFixtures) + if (fixture != null) { + decisionTableExecutor.execute(it, fixture) + } else { + null + } + } - val fixtureClasses = annotation.fixtureClasses - .filter { it.java.isAnnotationPresent(DecisionTableFixture::class.java) } + val scenarioFixtures = documentClassModel.scenarioFixtures + val scenarioResults = document.lists.mapNotNull { + val fixture = scenarioToFixtureMatcher.findMatchingFixture(it, scenarioFixtures) + if (fixture != null) { + scenarioExecutor.execute(it, fixture) + } else { + null + } + } - return tableMatcher.matchTablesToFixtures(tables, fixtureClasses) + return DocumentResult(decisionTableResults, scenarioResults) + } + private fun loadDocument(documentClassModel: ExecutableDocumentModel): Document { + return with(documentClassModel.documentIdentifier) { + repositoryManager.getRepository(repository).getDocument(id) + } } - @Throws(ExecutionException::class) - fun execute(document: Class<*>): DocumentResult { - val tableToFixture = validateInputAndMatchFixtures(document) +} + +private data class DocumentIdentifier( + val repository: String, + val id: String +) + +private data class ExecutableDocumentModel( + val documentIdentifier: DocumentIdentifier, + val decisionTableFixtures: List>, + val scenarioFixtures: List> +) { - val executor = DecisionTableExecutor() - val decisionTableResults = tableToFixture.map { (table, fixture) -> - executor.execute(table, fixture) + companion object { + + fun of(documentClass: Class<*>): ExecutableDocumentModel { + validate(documentClass) + return ExecutableDocumentModel( + documentIdentifier = getDocumentIdentifier(documentClass), + decisionTableFixtures = getDecisionTableFixtures(documentClass), + scenarioFixtures = getScenarioFixtures(documentClass) + ) + } + + private fun getDocumentIdentifier(document: Class<*>): DocumentIdentifier { + val annotation = document.executableDocumentAnnotation!! + val values = annotation.value.split("://") + .also { require(it.size == 2) { "Illegal annotation value '${annotation.value}'." } } + return DocumentIdentifier(values[0], values[1]) + } + + private fun validate(document: Class<*>) { + if (document.executableDocumentAnnotation == null) { + throw IllegalArgumentException("ExecutableDocument annotation is not present on class ${document.canonicalName}.") + } } - return DocumentResult(decisionTableResults = decisionTableResults) + private fun getDecisionTableFixtures(document: Class<*>) = getFixtures(document, DecisionTableFixture::class) + private fun getScenarioFixtures(document: Class<*>) = getFixtures(document, ScenarioFixture::class) + + private fun getFixtures(document: Class<*>, annotationClass: KClass): List> { + val declaredInside = document.declaredClasses + .filter { it.isAnnotationPresent(annotationClass.java) } + val fromAnnotation = document.executableDocumentAnnotation!!.fixtureClasses + .map { it.java } + .filter { it.isAnnotationPresent(annotationClass.java) } + return declaredInside + fromAnnotation + } + + private val Class<*>.executableDocumentAnnotation: ExecutableDocument? + get() = getAnnotation(ExecutableDocument::class.java) + } } diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/ScenarioToFixtureMatcher.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/ScenarioToFixtureMatcher.kt new file mode 100644 index 00000000..dd146af1 --- /dev/null +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/ScenarioToFixtureMatcher.kt @@ -0,0 +1,14 @@ +package org.livingdoc.engine + +import org.livingdoc.repositories.model.scenario.Scenario + +/** + * Default matcher to find the right fixture classes for a given list of tables. + */ +class ScenarioToFixtureMatcher { + + fun findMatchingFixture(scenario: Scenario, fixtures: List>): Class<*>? { + return null + } + +} diff --git a/livingdoc-sample/src/test/kotlin/org/livingdoc/example/CalculatorDocument.kt b/livingdoc-sample/src/test/kotlin/org/livingdoc/example/CalculatorDocument.kt index d1d17784..c4912d66 100644 --- a/livingdoc-sample/src/test/kotlin/org/livingdoc/example/CalculatorDocument.kt +++ b/livingdoc-sample/src/test/kotlin/org/livingdoc/example/CalculatorDocument.kt @@ -11,13 +11,7 @@ import org.livingdoc.api.fixtures.scenarios.Binding import org.livingdoc.api.fixtures.scenarios.ScenarioFixture import org.livingdoc.api.fixtures.scenarios.Step -@ExecutableDocument( - value = "local://Calculator.md", - fixtureClasses = [ - CalculatorDocument.CalculatorDecisionTableFixture::class, - CalculatorDocument.CalculatorScenarioFixture::class - ] -) +@ExecutableDocument("local://Calculator.md") class CalculatorDocument { @DecisionTableFixture From 30a82f045a0d211d9b586b89945512348f4e69db Mon Sep 17 00:00:00 2001 From: Stefan Ludwig Date: Sat, 25 Aug 2018 20:10:00 +0200 Subject: [PATCH 2/4] refactoring to prepare sequentially processable document structures --- .../kotlin/org/livingdoc/engine/LivingDoc.kt | 33 ++++++------- .../engine/execution/DocumentResult.kt | 6 +-- .../execution/examples/ExampleResult.kt | 4 ++ .../model/DecisionTableResult.kt | 9 ++-- .../scenarios/model/ScenarioResult.kt | 7 +-- .../ExecutableDocumentDescriptor.kt | 27 +++++----- .../org/livingdoc/repositories/Document.kt | 9 +--- .../livingdoc/repositories/HtmlDocument.kt | 10 ++-- .../livingdoc/repositories/model/Example.kt | 3 ++ .../model/decisiontable/DecisionTable.kt | 8 +-- .../repositories/model/scenario/Scenario.kt | 6 ++- .../repositories/RepositoryManagerTest.kt | 2 +- .../repositories/format/HtmlFormat.kt | 4 +- .../repositories/format/MarkdownFormat.kt | 8 +-- .../repositories/format/HtmlFormatTest.kt | 29 +++++------ .../repositories/format/MarkdownFormatTest.kt | 49 +++++++++---------- 16 files changed, 110 insertions(+), 104 deletions(-) create mode 100644 livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/ExampleResult.kt create mode 100644 livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/Example.kt diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt index 60fc5445..7109e868 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt @@ -5,11 +5,14 @@ import org.livingdoc.api.fixtures.decisiontables.DecisionTableFixture import org.livingdoc.api.fixtures.scenarios.ScenarioFixture import org.livingdoc.engine.execution.DocumentResult import org.livingdoc.engine.execution.ExecutionException +import org.livingdoc.engine.execution.examples.ExampleResult import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableExecutor import org.livingdoc.engine.execution.examples.scenarios.ScenarioExecutor import org.livingdoc.repositories.Document import org.livingdoc.repositories.RepositoryManager import org.livingdoc.repositories.config.Configuration +import org.livingdoc.repositories.model.decisiontable.DecisionTable +import org.livingdoc.repositories.model.scenario.Scenario import kotlin.reflect.KClass /** @@ -34,27 +37,21 @@ class LivingDoc( val document = loadDocument(documentClassModel) - val decisionTableFixtures = documentClassModel.decisionTableFixtures - val decisionTableResults = document.tables.mapNotNull { - val fixture = decisionTableToFixtureMatcher.findMatchingFixture(it, decisionTableFixtures) - if (fixture != null) { - decisionTableExecutor.execute(it, fixture) - } else { - null + val results: List = document.elements.mapNotNull { element -> + when (element) { + is DecisionTable -> { + decisionTableToFixtureMatcher.findMatchingFixture(element, documentClassModel.decisionTableFixtures) + ?.let { decisionTableExecutor.execute(element, it) } + } + is Scenario -> { + scenarioToFixtureMatcher.findMatchingFixture(element, documentClassModel.decisionTableFixtures) + ?.let { scenarioExecutor.execute(element, it) } + } + else -> null } } - val scenarioFixtures = documentClassModel.scenarioFixtures - val scenarioResults = document.lists.mapNotNull { - val fixture = scenarioToFixtureMatcher.findMatchingFixture(it, scenarioFixtures) - if (fixture != null) { - scenarioExecutor.execute(it, fixture) - } else { - null - } - } - - return DocumentResult(decisionTableResults, scenarioResults) + return DocumentResult(results) } private fun loadDocument(documentClassModel: ExecutableDocumentModel): Document { diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/DocumentResult.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/DocumentResult.kt index 35d14e43..4c7fe23b 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/DocumentResult.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/DocumentResult.kt @@ -1,9 +1,7 @@ package org.livingdoc.engine.execution -import org.livingdoc.engine.execution.examples.decisiontables.model.DecisionTableResult -import org.livingdoc.engine.execution.examples.scenarios.model.ScenarioResult +import org.livingdoc.engine.execution.examples.ExampleResult data class DocumentResult( - val decisionTableResults: List = emptyList(), - val scenarioResults: List = emptyList() + val results: List = emptyList() ) diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/ExampleResult.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/ExampleResult.kt new file mode 100644 index 00000000..849cde3d --- /dev/null +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/ExampleResult.kt @@ -0,0 +1,4 @@ +package org.livingdoc.engine.execution.examples + +interface ExampleResult { +} \ No newline at end of file diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/model/DecisionTableResult.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/model/DecisionTableResult.kt index a49d2a6d..ce23abe8 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/model/DecisionTableResult.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/model/DecisionTableResult.kt @@ -1,14 +1,15 @@ package org.livingdoc.engine.execution.examples.decisiontables.model import org.livingdoc.engine.execution.Result +import org.livingdoc.engine.execution.examples.ExampleResult import org.livingdoc.repositories.model.decisiontable.DecisionTable import org.livingdoc.repositories.model.decisiontable.Header data class DecisionTableResult( - val headers: List
, - val rows: List, - var result: Result = Result.Unknown -) { + val headers: List
, + val rows: List, + var result: Result = Result.Unknown +) : ExampleResult { companion object { fun from(decisionTable: DecisionTable): DecisionTableResult { diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/scenarios/model/ScenarioResult.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/scenarios/model/ScenarioResult.kt index e3f59083..e22b1e2c 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/scenarios/model/ScenarioResult.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/scenarios/model/ScenarioResult.kt @@ -1,12 +1,13 @@ package org.livingdoc.engine.execution.examples.scenarios.model import org.livingdoc.engine.execution.Result +import org.livingdoc.engine.execution.examples.ExampleResult import org.livingdoc.repositories.model.scenario.Scenario data class ScenarioResult( - val steps: List, - var result: Result = Result.Unknown -) { + val steps: List, + var result: Result = Result.Unknown +) : ExampleResult { companion object { fun from(scenario: Scenario): ScenarioResult { diff --git a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt index 3d2dbf3b..78257f2b 100644 --- a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt +++ b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt @@ -9,7 +9,9 @@ import org.livingdoc.api.fixtures.decisiontables.DecisionTableFixture import org.livingdoc.api.fixtures.scenarios.ScenarioFixture import org.livingdoc.engine.execution.DocumentResult import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableExecutor +import org.livingdoc.engine.execution.examples.decisiontables.model.DecisionTableResult import org.livingdoc.engine.execution.examples.scenarios.ScenarioExecutor +import org.livingdoc.engine.execution.examples.scenarios.model.ScenarioResult import org.livingdoc.junit.engine.LivingDocContext import org.livingdoc.repositories.model.decisiontable.DecisionTable import org.livingdoc.repositories.model.decisiontable.Field @@ -29,16 +31,19 @@ class ExecutableDocumentDescriptor( val result = context.livingDoc.execute(documentClass) // val result = dummyExecution() - result.decisionTableResults.forEachIndexed { index, tableResult -> - val descriptor = DecisionTableTestDescriptor(tableUniqueId(index), tableDisplayName(index), tableResult) - .also { it.setParent(this) } - dynamicTestExecutor.execute(descriptor) - } - - result.scenarioResults.forEachIndexed { index, scenarioResult -> - val descriptor = ScenarioTestDescriptor(scenarioUniqueId(index), scenarioDisplayName(index), scenarioResult) - .also { it.setParent(this) } - dynamicTestExecutor.execute(descriptor) + result.results.forEachIndexed { index, exampleResult -> + when (exampleResult) { + is DecisionTableResult -> { + val descriptor = DecisionTableTestDescriptor(tableUniqueId(index), tableDisplayName(index), exampleResult) + .also { it.setParent(this) } + dynamicTestExecutor.execute(descriptor) + } + is ScenarioResult -> { + val descriptor = ScenarioTestDescriptor(scenarioUniqueId(index), scenarioDisplayName(index), exampleResult) + .also { it.setParent(this) } + dynamicTestExecutor.execute(descriptor) + } + } } return context @@ -96,7 +101,7 @@ class ExecutableDocumentDescriptor( val scenarioResult = ScenarioExecutor() .execute(scenario, scenarioFixture, documentClass) - return DocumentResult(listOf(decisionTableResult), listOf(scenarioResult)) + return DocumentResult(listOf(decisionTableResult) + listOf(scenarioResult)) } diff --git a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/Document.kt b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/Document.kt index 9eaa8510..18cf1519 100644 --- a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/Document.kt +++ b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/Document.kt @@ -1,10 +1,3 @@ package org.livingdoc.repositories -import org.livingdoc.repositories.model.decisiontable.DecisionTable -import org.livingdoc.repositories.model.scenario.Scenario - -open class Document(val tables: List, val lists: List) { - operator fun component1() = tables - operator fun component2() = lists -} - +open class Document(val elements: List) diff --git a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/HtmlDocument.kt b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/HtmlDocument.kt index 5ebc6e82..3cf0a90d 100644 --- a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/HtmlDocument.kt +++ b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/HtmlDocument.kt @@ -1,10 +1,6 @@ package org.livingdoc.repositories -import org.livingdoc.repositories.model.decisiontable.DecisionTable -import org.livingdoc.repositories.model.scenario.Scenario - class HtmlDocument( - tables: List, - lists: List, - val jsoupDoc: org.jsoup.nodes.Document -) : Document(tables, lists) + elements: List, + val jsoupDoc: org.jsoup.nodes.Document +) : Document(elements) diff --git a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/Example.kt b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/Example.kt new file mode 100644 index 00000000..ba91714d --- /dev/null +++ b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/Example.kt @@ -0,0 +1,3 @@ +package org.livingdoc.repositories.model + +interface Example \ No newline at end of file diff --git a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/DecisionTable.kt b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/DecisionTable.kt index 1ccae547..d2e8f366 100644 --- a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/DecisionTable.kt +++ b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/DecisionTable.kt @@ -1,6 +1,8 @@ package org.livingdoc.repositories.model.decisiontable +import org.livingdoc.repositories.model.Example + data class DecisionTable( - val headers: List
, - val rows: List -) + val headers: List
, + val rows: List +) : Example diff --git a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/scenario/Scenario.kt b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/scenario/Scenario.kt index 2c16684d..69f67fbb 100644 --- a/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/scenario/Scenario.kt +++ b/livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/model/scenario/Scenario.kt @@ -1,5 +1,7 @@ package org.livingdoc.repositories.model.scenario +import org.livingdoc.repositories.model.Example + data class Scenario( - val steps: List -) + val steps: List +) : Example diff --git a/livingdoc-repositories/src/test/kotlin/org/livingdoc/repositories/RepositoryManagerTest.kt b/livingdoc-repositories/src/test/kotlin/org/livingdoc/repositories/RepositoryManagerTest.kt index 84a47db3..b26a1c15 100644 --- a/livingdoc-repositories/src/test/kotlin/org/livingdoc/repositories/RepositoryManagerTest.kt +++ b/livingdoc-repositories/src/test/kotlin/org/livingdoc/repositories/RepositoryManagerTest.kt @@ -174,7 +174,7 @@ internal class RepositoryManagerTest { } class TestRepository(val config: TestRepositoryConfiguration) : DocumentRepository { - override fun getDocument(documentIdentifier: String): Document = Document(emptyList(), emptyList()) + override fun getDocument(documentIdentifier: String): Document = Document(emptyList()) } data class TestRepositoryConfiguration( diff --git a/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/HtmlFormat.kt b/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/HtmlFormat.kt index 186c7809..b1dcccb7 100644 --- a/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/HtmlFormat.kt +++ b/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/HtmlFormat.kt @@ -27,7 +27,9 @@ class HtmlFormat : DocumentFormat { override fun parse(stream: InputStream): HtmlDocument { val streamContent = stream.readBytes().toString(Charset.defaultCharset()) val document = Jsoup.parse(streamContent) - return HtmlDocument(parseTables(document), parseLists(document), document) + val elements = parseTables(document) + parseLists(document) + // TODO: provide elements in order they occur inside the original source document + return HtmlDocument(elements, document) } private fun parseTables(document: Document): List { diff --git a/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/MarkdownFormat.kt b/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/MarkdownFormat.kt index f1bbb34b..cc47fa97 100644 --- a/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/MarkdownFormat.kt +++ b/livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/format/MarkdownFormat.kt @@ -7,8 +7,9 @@ import com.vladsch.flexmark.ext.tables.TableRow import com.vladsch.flexmark.ext.tables.TablesExtension import com.vladsch.flexmark.parser.Parser import com.vladsch.flexmark.util.options.MutableDataSet -import org.livingdoc.repositories.* import org.livingdoc.repositories.Document +import org.livingdoc.repositories.DocumentFormat +import org.livingdoc.repositories.ParseException import org.livingdoc.repositories.model.decisiontable.DecisionTable import org.livingdoc.repositories.model.decisiontable.Field import org.livingdoc.repositories.model.decisiontable.Header @@ -30,8 +31,9 @@ class MarkdownFormat : DocumentFormat { val parser = Parser.builder(options).build() val text = stream.reader().use { it.readText() } val (tables, scenarios) = parser.parse(text).mapToNodes() - - return Document(tables, scenarios) + val elements = tables + scenarios + // TODO: provide elements in order they occur inside the original source document + return Document(elements) } private fun com.vladsch.flexmark.ast.Node.mapToNodes(): Pair, List> { diff --git a/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/HtmlFormatTest.kt b/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/HtmlFormatTest.kt index 635d7ee3..17299045 100644 --- a/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/HtmlFormatTest.kt +++ b/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/HtmlFormatTest.kt @@ -17,6 +17,7 @@ import org.livingdoc.repositories.format.HtmlFormatTestData.getHtmlWithUnordered import org.livingdoc.repositories.format.HtmlFormatTestData.getHtmlWithUnorderedListContainsOnlyOneItem import org.livingdoc.repositories.format.HtmlFormatTestData.getValidHtml import org.livingdoc.repositories.model.decisiontable.DecisionTable +import org.livingdoc.repositories.model.scenario.Scenario class HtmlFormatTest { @@ -26,7 +27,7 @@ class HtmlFormatTest { @Test fun `tables with one row are ignored`() { val result = cut.parse(getHtmlTableWithOnlyOneRow()) - assertThat(result.tables).hasSize(0) + assertThat(result.elements).hasSize(0) } @Test @@ -51,7 +52,7 @@ class HtmlFormatTest { fun `parse Html into DecisionTable`() { val htmlDocument = cut.parse(getValidHtml()) - val documentNode = htmlDocument.tables[0] + val documentNode = htmlDocument.elements[0] as DecisionTable assertThat(documentNode).isInstanceOf(DecisionTable::class.java) assertThat(documentNode.headers).extracting("name").containsExactly("Firstname", "Lastname", "Age") @@ -61,16 +62,16 @@ class HtmlFormatTest { assertThat(documentNode.rows[1].headerToField).hasSize(3) assertThat(documentNode.rows[0].headerToField.map { it.key.name }).containsExactly( - "Firstname", - "Lastname", - "Age" + "Firstname", + "Lastname", + "Age" ) assertThat(documentNode.rows[0].headerToField.map { it.value.value }).containsExactly("Jill", "Smith", "50") assertThat(documentNode.rows[1].headerToField.map { it.key.name }).containsExactly( - "Firstname", - "Lastname", - "Age" + "Firstname", + "Lastname", + "Age" ) assertThat(documentNode.rows[1].headerToField.map { it.value.value }).containsExactly("Eve", "Jackson", "94") } @@ -79,7 +80,7 @@ class HtmlFormatTest { fun `parse unorderedList into Scenario`() { val htmlDocument = cut.parse(getHtmlWithUnorderedList()) - val scenario = htmlDocument.lists[0] + val scenario = htmlDocument.elements[0] as Scenario assertThat(scenario.steps).isNotNull assertThat(scenario.steps).hasSize(5) @@ -93,7 +94,7 @@ class HtmlFormatTest { @Test fun `parse orderedList into Scenario`() { val htmlDocument = cut.parse(getHtmlWithOrderedList()) - val scenario = htmlDocument.lists[0] + val scenario = htmlDocument.elements[0] as Scenario assertThat(scenario.steps).isNotNull assertThat(scenario.steps).hasSize(5) @@ -108,16 +109,16 @@ class HtmlFormatTest { fun `unordered list with only one item is ignored`() { val htmlDocument = cut.parse(getHtmlWithUnorderedListContainsOnlyOneItem()) - assertThat(htmlDocument.lists).isNotNull - assertThat(htmlDocument.lists).isEmpty() + assertThat(htmlDocument.elements).isNotNull + assertThat(htmlDocument.elements).isEmpty() } @Test fun `ordered list with only one item is ignored`() { val htmlDocument = cut.parse(getHtmlWithOrderedListContainsOnlyOneItem()) - assertThat(htmlDocument.lists).isNotNull - assertThat(htmlDocument.lists).isEmpty() + assertThat(htmlDocument.elements).isNotNull + assertThat(htmlDocument.elements).isEmpty() } @Test diff --git a/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/MarkdownFormatTest.kt b/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/MarkdownFormatTest.kt index 3158b4d5..7e343256 100644 --- a/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/MarkdownFormatTest.kt +++ b/livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/format/MarkdownFormatTest.kt @@ -2,7 +2,9 @@ package org.livingdoc.repositories.format import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.livingdoc.repositories.model.decisiontable.DecisionTable import org.livingdoc.repositories.model.decisiontable.Header +import org.livingdoc.repositories.model.scenario.Scenario internal class MarkdownFormatTest { @@ -10,7 +12,7 @@ internal class MarkdownFormatTest { fun `Table is read successfully`() { val mdFormat = MarkdownFormat() val document = mdFormat.parse( - """ + """ # Irrelevant Headline ``` @@ -28,18 +30,18 @@ internal class MarkdownFormatTest { """.trimIndent().byteInputStream() ) - val (tables, scenarios) = document + val elements = document.elements - assertThat(tables).hasSize(1) - assertThat(scenarios).isEmpty() + assertThat(elements).hasSize(1) - assertThat(tables[0].headers.map(Header::name)).containsExactly("Column1", "Column2", "Column3") - tables[0].rows.also { - assertThat(it[0].headerToField.map { it.key.name }).containsExactly("Column1", "Column2", "Column3") - assertThat(it[0].headerToField.map { it.value.value }).containsExactly("Cell11", "Cell12", "Cell13") + val firstElement = elements[0] as DecisionTable + assertThat(firstElement.headers.map(Header::name)).containsExactly("Column1", "Column2", "Column3") + firstElement.rows.also { rows -> + assertThat(rows[0].headerToField.map { it.key.name }).containsExactly("Column1", "Column2", "Column3") + assertThat(rows[0].headerToField.map { it.value.value }).containsExactly("Cell11", "Cell12", "Cell13") - assertThat(it[1].headerToField.map { it.key.name }).containsExactly("Column1", "Column2", "Column3") - assertThat(it[1].headerToField.map { it.value.value }).containsExactly("Cell21", "Cell22", "Cell23") + assertThat(rows[1].headerToField.map { it.key.name }).containsExactly("Column1", "Column2", "Column3") + assertThat(rows[1].headerToField.map { it.value.value }).containsExactly("Cell21", "Cell22", "Cell23") } } @@ -47,7 +49,7 @@ internal class MarkdownFormatTest { fun `Bullet list is read successfully`() { val mdFormat = MarkdownFormat() val document = mdFormat.parse( - """ + """ # Irrelevant Headline ``` @@ -79,10 +81,9 @@ internal class MarkdownFormatTest { """.trimIndent().byteInputStream() ) - val (tables, scenarios) = document + val elements = document.elements - assertThat(tables).isEmpty() - assertThat(scenarios).hasSize(1) + assertThat(elements).hasSize(1) val thirdSentence = """ Listitem3 @@ -96,7 +97,7 @@ internal class MarkdownFormatTest { """.trimIndent() - val scenario = scenarios[0] + val scenario = elements[0] as Scenario scenario.steps.also { assertThat(it).hasSize(4) assertThat(it[0].value).isEqualTo("Listitem1\nSentence in first list item.") @@ -110,7 +111,7 @@ internal class MarkdownFormatTest { fun `Numbered list is read successfully`() { val mdFormat = MarkdownFormat() val document = mdFormat.parse( - """ + """ # Irrelevant Headline ``` @@ -142,10 +143,9 @@ internal class MarkdownFormatTest { """.trimIndent().byteInputStream() ) - val (tables, scenarios) = document + val elements = document.elements - assertThat(tables).isEmpty() - assertThat(scenarios).hasSize(1) + assertThat(elements).hasSize(1) val thirdSentence = """ Listitem3 @@ -159,7 +159,7 @@ internal class MarkdownFormatTest { """.trimIndent() - val scenario = scenarios[0] + val scenario = elements[0] as Scenario assertThat(scenario.steps).hasSize(4) assertThat(scenario.steps[0].value).isEqualTo("Listitem1\nSentence in first list item.") assertThat(scenario.steps[1].value).isEqualTo("Listitem2\nSentence in second list item.") @@ -171,7 +171,7 @@ internal class MarkdownFormatTest { fun `Column table is read as scenario`() { val mdFormat = MarkdownFormat() val document = mdFormat.parse( - """ + """ # Irrelevant Headline ``` @@ -189,12 +189,11 @@ internal class MarkdownFormatTest { """.trimIndent().byteInputStream() ) - val (tables, scenarios) = document + val elements = document.elements - assertThat(tables).isEmpty() - assertThat(scenarios).hasSize(1) + assertThat(elements).hasSize(1) - val scenario = scenarios[0] + val scenario = elements[0] as Scenario scenario.steps.also { assertThat(it).hasSize(3) assertThat(it[0].value).isEqualTo("Column1") From e7ccc3d6a11005a496e9e7d4de8354090d928225 Mon Sep 17 00:00:00 2001 From: Stefan Ludwig Date: Sat, 25 Aug 2018 20:41:22 +0200 Subject: [PATCH 3/4] refactoring to simplify matching of decision tables (100% matches only - to begin with) --- .../engine/DecisionTableToFixtureMatcher.kt | 83 +++++-------------- .../DecisionTableFixtureModel.kt | 5 +- 2 files changed, 24 insertions(+), 64 deletions(-) diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt index fa1a56db..20b12ae5 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/DecisionTableToFixtureMatcher.kt @@ -2,7 +2,6 @@ package org.livingdoc.engine import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableFixtureModel import org.livingdoc.repositories.model.decisiontable.DecisionTable -import org.livingdoc.repositories.model.decisiontable.Header /** * Default matcher to find the right fixture classes for a given list of tables. @@ -10,68 +9,26 @@ import org.livingdoc.repositories.model.decisiontable.Header class DecisionTableToFixtureMatcher { fun findMatchingFixture(decisionTable: DecisionTable, fixtures: List>): Class<*>? { - return matchTablesToFixtures(listOf(decisionTable), fixtures)[decisionTable] + val headerNames = decisionTable.headers.map { it.name } + val numberOfHeaders = headerNames.size + + val matchingFixtures = fixtures.filter { fixtureClass -> + val fixtureModel = DecisionTableFixtureModel(fixtureClass) + val aliases = fixtureModel.aliases + val numberOfAliases = aliases.size + val numberOfMatchedHeaders = headerNames.filter { aliases.contains(it) }.size + + numberOfMatchedHeaders == numberOfHeaders && numberOfMatchedHeaders == numberOfAliases + } + + if (matchingFixtures.size > 1) { + throw MultipleMatchingFixturesException(headerNames, matchingFixtures) + } + return matchingFixtures.firstOrNull() } - /** - * Finds the fitting fixture for the given decision tables. - */ - fun matchTablesToFixtures(tables: List, fixtures: List>): Map> { - /* - * - Start with a list of Tables and a list of Fixtures - * - Create mapping Model -> Fixture (straightforward) - * - Create mapping Table -> Model (find table that contains all aliases of the model) - * - Create mapping Table -> Fixture (via Table -> Model -> Fixture) - */ - val fixtureModelToFixtureClass = mapModelsToFixtures(fixtures) - val decisionTableToFixtureModel = mapTablesToModels(fixtureModelToFixtureClass.keys, tables) - return mapTablesToFixtures(decisionTableToFixtureModel, fixtureModelToFixtureClass) - } - - private fun mapTablesToFixtures( - decisionTableToFixtureModel: Map, - fixtureModelToFixtureClass: Map?> - ): Map> = - decisionTableToFixtureModel.keys.map { table -> - val model: DecisionTableFixtureModel = decisionTableToFixtureModel[table] - ?: throw NoDecisionTableModelForDecisionTable(table) - val fixture: Class<*> = fixtureModelToFixtureClass[model] - ?: throw NoFixtureForDecisionTableModelException(model) - table to fixture - }.toMap() - - private fun mapModelsToFixtures(fixtureClasses: List>) = - fixtureClasses.associateByTo(mutableMapOf()) { clazz -> - DecisionTableFixtureModel(clazz) - } - - private fun mapTablesToModels(tableModels: Iterable, tables: Iterable) = - tableModels.associateByTo(mutableMapOf()) { model -> - val table = tables.firstOrNull { (headers, _) -> - headers.map(Header::name).containsAll(model.inputAliases) - } ?: throw NoDecisionTableForDecisionTableModelException(model) - table - } -} - -/** - * This exception is thrown when the [DecisionTableToFixtureMatcher] cannot find a [DecisionTable] that fits - * to a [DecisionTableFixtureModel] of a Fixture class. - */ -class NoDecisionTableForDecisionTableModelException(model: DecisionTableFixtureModel) - : java.lang.IllegalArgumentException("Could not find a Decision Table for Fixture Class ${model.fixtureClass.canonicalName}") - -/** - * This exception is thrown when the [DecisionTableToFixtureMatcher] cannot find a [DecisionTableFixtureModel] that fits - * to a [DecisionTable]. - */ -class NoDecisionTableModelForDecisionTable(table: DecisionTable) - : java.lang.IllegalArgumentException("Could not find a Decision Table Model for Decision Table $table") - -/** - * This exception is thrown when the [DecisionTableToFixtureMatcher] cannot find a Fixture class that fits - * to a [DecisionTableFixtureModel]. - */ -class NoFixtureForDecisionTableModelException(model: DecisionTableFixtureModel) - : java.lang.IllegalArgumentException("Could not find a Decision Table Model for Decision Table ${model.fixtureClass.canonicalName}") + class MultipleMatchingFixturesException(headerNames: List, matchingFixtures: List>) + : RuntimeException("Could not identify a unique fixture matching the Decision Table's headers " + + "${headerNames.map { "'$it'" }}. Matching fixtures found: $matchingFixtures") +} \ No newline at end of file diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/DecisionTableFixtureModel.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/DecisionTableFixtureModel.kt index adfda851..e16993b6 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/DecisionTableFixtureModel.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/decisiontables/DecisionTableFixtureModel.kt @@ -17,7 +17,7 @@ class DecisionTableFixtureModel( val inputFields: List val inputMethods: List - val inputAliases: MutableSet + private val inputAliases: MutableSet private val inputAliasToField: MutableMap private val inputAliasToMethod: MutableMap @@ -25,6 +25,9 @@ class DecisionTableFixtureModel( private val checkAliases: Set private val checkAliasToMethod: Map + val aliases: Set + get() = inputAliases + checkAliases + init { // method analysis From f780dcc72775df7ddde96375b73f4c05c60261de Mon Sep 17 00:00:00 2001 From: Stefan Ludwig Date: Sat, 25 Aug 2018 21:19:29 +0200 Subject: [PATCH 4/4] added alternate execution mode to LivingDoc allowing the JUnit Engine to dictate when an example is actually executed --- .../kotlin/org/livingdoc/engine/LivingDoc.kt | 62 ++++++++--- .../DecisionTableTestDescriptor.kt | 15 +-- .../ExecutableDocumentDescriptor.kt | 101 ++++-------------- .../descriptors/ScenarioTestDescriptor.kt | 15 +-- 4 files changed, 76 insertions(+), 117 deletions(-) diff --git a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt index 7109e868..43b46aba 100644 --- a/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt +++ b/livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt @@ -7,10 +7,13 @@ import org.livingdoc.engine.execution.DocumentResult import org.livingdoc.engine.execution.ExecutionException import org.livingdoc.engine.execution.examples.ExampleResult import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableExecutor +import org.livingdoc.engine.execution.examples.decisiontables.model.DecisionTableResult import org.livingdoc.engine.execution.examples.scenarios.ScenarioExecutor +import org.livingdoc.engine.execution.examples.scenarios.model.ScenarioResult import org.livingdoc.repositories.Document import org.livingdoc.repositories.RepositoryManager import org.livingdoc.repositories.config.Configuration +import org.livingdoc.repositories.model.Example import org.livingdoc.repositories.model.decisiontable.DecisionTable import org.livingdoc.repositories.model.scenario.Scenario import kotlin.reflect.KClass @@ -32,26 +35,20 @@ class LivingDoc( ) { @Throws(ExecutionException::class) - fun execute(documentClass: Class<*>): DocumentResult { - val documentClassModel = ExecutableDocumentModel.of(documentClass) + fun execute(documentClass: Class<*>) = DocumentResult(getExecutableExamples(documentClass).map { it.execute() }) + @Throws(ExecutionException::class) + fun getExecutableExamples(documentClass: Class<*>): List> { + val documentClassModel = ExecutableDocumentModel.of(documentClass) val document = loadDocument(documentClassModel) - - val results: List = document.elements.mapNotNull { element -> + return document.elements.mapNotNull { element -> when (element) { - is DecisionTable -> { - decisionTableToFixtureMatcher.findMatchingFixture(element, documentClassModel.decisionTableFixtures) - ?.let { decisionTableExecutor.execute(element, it) } - } - is Scenario -> { - scenarioToFixtureMatcher.findMatchingFixture(element, documentClassModel.decisionTableFixtures) - ?.let { scenarioExecutor.execute(element, it) } - } + is DecisionTable -> executableDecisionTable(element, documentClassModel) + is Scenario -> executableScenario(element, documentClassModel) else -> null } } - return DocumentResult(results) } private fun loadDocument(documentClassModel: ExecutableDocumentModel): Document { @@ -60,14 +57,52 @@ class LivingDoc( } } + private fun executableDecisionTable(element: DecisionTable, documentModel: ExecutableDocumentModel): ExecutableDecisionTable? { + val matchingFixture = decisionTableToFixtureMatcher.findMatchingFixture(element, documentModel.decisionTableFixtures) + if (matchingFixture != null) { + return ExecutableDecisionTable(element) { + decisionTableExecutor.execute(it, matchingFixture, documentModel.documentClass) + } + } + return null + } + + private fun executableScenario(scenario: Scenario, documentModel: ExecutableDocumentModel): ExecutableScenario? { + val matchingFixture = scenarioToFixtureMatcher.findMatchingFixture(scenario, documentModel.decisionTableFixtures) + if (matchingFixture != null) { + return ExecutableScenario(scenario) { + scenarioExecutor.execute(it, matchingFixture, documentModel.documentClass) + } + } + return null + } + +} + +sealed class ExecutableExample( + val example: A, + private val execution: (A) -> B +) { + fun execute(): B = execution(example) } +class ExecutableDecisionTable( + example: DecisionTable, + execution: (DecisionTable) -> DecisionTableResult +) : ExecutableExample(example, execution) + +class ExecutableScenario( + example: Scenario, + execution: (Scenario) -> ScenarioResult +) : ExecutableExample(example, execution) + private data class DocumentIdentifier( val repository: String, val id: String ) private data class ExecutableDocumentModel( + val documentClass: Class<*>, val documentIdentifier: DocumentIdentifier, val decisionTableFixtures: List>, val scenarioFixtures: List> @@ -78,6 +113,7 @@ private data class ExecutableDocumentModel( fun of(documentClass: Class<*>): ExecutableDocumentModel { validate(documentClass) return ExecutableDocumentModel( + documentClass = documentClass, documentIdentifier = getDocumentIdentifier(documentClass), decisionTableFixtures = getDecisionTableFixtures(documentClass), scenarioFixtures = getScenarioFixtures(documentClass) diff --git a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/DecisionTableTestDescriptor.kt b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/DecisionTableTestDescriptor.kt index 700461bd..1cba83d8 100644 --- a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/DecisionTableTestDescriptor.kt +++ b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/DecisionTableTestDescriptor.kt @@ -6,8 +6,8 @@ import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor import org.junit.platform.engine.support.hierarchical.Node import org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip import org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip +import org.livingdoc.engine.ExecutableDecisionTable import org.livingdoc.engine.execution.Result -import org.livingdoc.engine.execution.examples.decisiontables.model.DecisionTableResult import org.livingdoc.engine.execution.examples.decisiontables.model.FieldResult import org.livingdoc.engine.execution.examples.decisiontables.model.RowResult import org.livingdoc.junit.engine.LivingDocContext @@ -16,13 +16,13 @@ import org.livingdoc.repositories.model.decisiontable.Header class DecisionTableTestDescriptor( uniqueId: UniqueId, displayName: String, - private val tableResult: DecisionTableResult + private val decisionTable: ExecutableDecisionTable ) : AbstractTestDescriptor(uniqueId, displayName), Node { override fun getType() = TestDescriptor.Type.CONTAINER override fun execute(context: LivingDocContext, dynamicTestExecutor: Node.DynamicTestExecutor): LivingDocContext { - tableResult.rows.forEachIndexed { index, rowResult -> + decisionTable.execute().rows.forEachIndexed { index, rowResult -> val descriptor = RowTestDescriptor(rowUniqueId(index), rowDisplayName(index), rowResult) .also { it.setParent(this) } dynamicTestExecutor.execute(descriptor) @@ -33,15 +33,6 @@ class DecisionTableTestDescriptor( private fun rowUniqueId(index: Int) = uniqueId.append("row", "$index") private fun rowDisplayName(index: Int) = "Row #${index + 1}" - override fun shouldBeSkipped(context: LivingDocContext): Node.SkipResult { - val result = tableResult.result - return when (result) { - Result.Unknown -> skip("unknown") - Result.Skipped -> skip("skipped") - else -> doNotSkip() - } - } - class RowTestDescriptor( uniqueId: UniqueId, displayName: String, diff --git a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt index 78257f2b..ddbac17c 100644 --- a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt +++ b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ExecutableDocumentDescriptor.kt @@ -5,20 +5,9 @@ import org.junit.platform.engine.UniqueId import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor import org.junit.platform.engine.support.hierarchical.Node import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor -import org.livingdoc.api.fixtures.decisiontables.DecisionTableFixture -import org.livingdoc.api.fixtures.scenarios.ScenarioFixture -import org.livingdoc.engine.execution.DocumentResult -import org.livingdoc.engine.execution.examples.decisiontables.DecisionTableExecutor -import org.livingdoc.engine.execution.examples.decisiontables.model.DecisionTableResult -import org.livingdoc.engine.execution.examples.scenarios.ScenarioExecutor -import org.livingdoc.engine.execution.examples.scenarios.model.ScenarioResult +import org.livingdoc.engine.ExecutableDecisionTable +import org.livingdoc.engine.ExecutableScenario import org.livingdoc.junit.engine.LivingDocContext -import org.livingdoc.repositories.model.decisiontable.DecisionTable -import org.livingdoc.repositories.model.decisiontable.Field -import org.livingdoc.repositories.model.decisiontable.Header -import org.livingdoc.repositories.model.decisiontable.Row -import org.livingdoc.repositories.model.scenario.Scenario -import org.livingdoc.repositories.model.scenario.Step class ExecutableDocumentDescriptor( uniqueId: UniqueId, @@ -28,81 +17,33 @@ class ExecutableDocumentDescriptor( override fun getType() = Type.CONTAINER_AND_TEST override fun execute(context: LivingDocContext, dynamicTestExecutor: DynamicTestExecutor): LivingDocContext { - val result = context.livingDoc.execute(documentClass) - // val result = dummyExecution() - - result.results.forEachIndexed { index, exampleResult -> - when (exampleResult) { - is DecisionTableResult -> { - val descriptor = DecisionTableTestDescriptor(tableUniqueId(index), tableDisplayName(index), exampleResult) - .also { it.setParent(this) } - dynamicTestExecutor.execute(descriptor) - } - is ScenarioResult -> { - val descriptor = ScenarioTestDescriptor(scenarioUniqueId(index), scenarioDisplayName(index), exampleResult) - .also { it.setParent(this) } - dynamicTestExecutor.execute(descriptor) + context.livingDoc + .getExecutableExamples(documentClass) + .forEachIndexed { index, example -> + when (example) { + is ExecutableDecisionTable -> execute(index, dynamicTestExecutor, example) + is ExecutableScenario -> execute(index, dynamicTestExecutor, example) + } } - } - } - return context } + private fun execute(index: Int, dynamicTestExecutor: DynamicTestExecutor, example: ExecutableScenario) { + val descriptor = ScenarioTestDescriptor(scenarioUniqueId(index), scenarioDisplayName(index), example) + .also { it.setParent(this) } + dynamicTestExecutor.execute(descriptor) + } + + private fun execute(index: Int, dynamicTestExecutor: DynamicTestExecutor, example: ExecutableDecisionTable) { + val descriptor = DecisionTableTestDescriptor(tableUniqueId(index), tableDisplayName(index), example) + .also { it.setParent(this) } + dynamicTestExecutor.execute(descriptor) + } + private fun tableUniqueId(index: Int) = uniqueId.append("table", "$index") private fun tableDisplayName(index: Int) = "Table #${index + 1}" private fun scenarioUniqueId(index: Int) = uniqueId.append("scenario", "$index") private fun scenarioDisplayName(index: Int) = "Scenario #${index + 1}" - // TODO: remove as soon as it is no longer need - private fun dummyExecution(): DocumentResult { - - val headerA = Header("a") - val headerB = Header("b") - val headerAplusB = Header("a + b = ?") - val headerAminusB = Header("a - b = ?") - val headerAtimesB = Header("a * b = ?") - val headerAdividedByB = Header("a / b = ?") - val decisionTable = DecisionTable( - listOf(headerA, headerB, headerAplusB, headerAminusB, headerAtimesB, headerAdividedByB), - listOf( - Row(mapOf( - headerA to Field("10"), - headerB to Field("5"), - headerAplusB to Field("15"), - headerAminusB to Field("5"), - headerAtimesB to Field("50"), - headerAdividedByB to Field("2") - )), - Row(mapOf( - headerA to Field("-3"), - headerB to Field("2"), - headerAplusB to Field("-1"), - headerAminusB to Field("-5"), - headerAtimesB to Field("-6"), - headerAdividedByB to Field("-1.5") - )) - ) - ) - val decisionTableFixture = documentClass.declaredClasses - .single { it.isAnnotationPresent(DecisionTableFixture::class.java) } - val decisionTableResult = DecisionTableExecutor() - .execute(decisionTable, decisionTableFixture, documentClass) - - val scenario = Scenario(listOf( - Step("adding 1 and 2 equals 3"), - Step("subtraction 5 form 15 equals 10"), - Step("multiplying 3 and 6 equals 18"), - Step("dividing 20 by 5 equals 4") - )) - val scenarioFixture = documentClass.declaredClasses - .single { it.isAnnotationPresent(ScenarioFixture::class.java) } - val scenarioResult = ScenarioExecutor() - .execute(scenario, scenarioFixture, documentClass) - - return DocumentResult(listOf(decisionTableResult) + listOf(scenarioResult)) - - } - } \ No newline at end of file diff --git a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ScenarioTestDescriptor.kt b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ScenarioTestDescriptor.kt index dfaddf99..98e065d5 100644 --- a/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ScenarioTestDescriptor.kt +++ b/livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/ScenarioTestDescriptor.kt @@ -6,21 +6,21 @@ import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor import org.junit.platform.engine.support.hierarchical.Node import org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip import org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip +import org.livingdoc.engine.ExecutableScenario import org.livingdoc.engine.execution.Result -import org.livingdoc.engine.execution.examples.scenarios.model.ScenarioResult import org.livingdoc.engine.execution.examples.scenarios.model.StepResult import org.livingdoc.junit.engine.LivingDocContext class ScenarioTestDescriptor( uniqueId: UniqueId, displayName: String, - private val scenarioResult: ScenarioResult + private val scenario: ExecutableScenario ) : AbstractTestDescriptor(uniqueId, displayName), Node { override fun getType() = TestDescriptor.Type.CONTAINER override fun execute(context: LivingDocContext, dynamicTestExecutor: Node.DynamicTestExecutor): LivingDocContext { - scenarioResult.steps.forEachIndexed { index, stepResult -> + scenario.execute().steps.forEachIndexed { index, stepResult -> val descriptor = StepTestDescriptor(stepUniqueId(index), stepDisplayName(stepResult), stepResult) .also { it.setParent(this) } dynamicTestExecutor.execute(descriptor) @@ -31,15 +31,6 @@ class ScenarioTestDescriptor( private fun stepUniqueId(index: Int) = uniqueId.append("step", "$index") private fun stepDisplayName(stepResult: StepResult) = stepResult.value - override fun shouldBeSkipped(context: LivingDocContext): Node.SkipResult { - val result = scenarioResult.result - return when (result) { - Result.Unknown -> skip("unknown") - Result.Skipped -> skip("skipped") - else -> doNotSkip() - } - } - class StepTestDescriptor( uniqueId: UniqueId, displayName: String,