Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 49 additions & 13 deletions livingdoc-engine/src/main/kotlin/org/livingdoc/engine/LivingDoc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ExecutableExample<*, *>> {
val documentClassModel = ExecutableDocumentModel.of(documentClass)
val document = loadDocument(documentClassModel)

val results: List<ExampleResult> = 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 {
Expand All @@ -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<A : Example, B : ExampleResult>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've been pushing for keeping this class small and straightforward, now it's filled with private sealed companions. We should discuss how to take this apart again.

val example: A,
private val execution: (A) -> B
) {
fun execute(): B = execution(example)
}

class ExecutableDecisionTable(
example: DecisionTable,
execution: (DecisionTable) -> DecisionTableResult
) : ExecutableExample<DecisionTable, DecisionTableResult>(example, execution)

class ExecutableScenario(
example: Scenario,
execution: (Scenario) -> ScenarioResult
) : ExecutableExample<Scenario, ScenarioResult>(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<Class<*>>,
val scenarioFixtures: List<Class<*>>
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<LivingDocContext> {

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)
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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))

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<LivingDocContext> {

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)
Expand All @@ -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,
Expand Down