diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Prov.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Prov.kt index b45b6c3..608c462 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Prov.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Prov.kt @@ -87,15 +87,15 @@ open class Prov protected constructor( /** * defines a task, which returns the returned result, the results of sub-tasks are not considered */ - fun requireLast(a: Prov.() -> ProvResult): ProvResult { - return evaluate(ResultMode.LAST) { a() } + fun requireLast(name: String? = null, a: Prov.() -> ProvResult): ProvResult { + return evaluate(ResultMode.LAST, name) { a() } } /** * defines a task, which always returns success */ - fun optional(a: Prov.() -> ProvResult): ProvResult { - return evaluate(ResultMode.OPTIONAL) { a() } + fun optional(name: String? = null, a: Prov.() -> ProvResult): ProvResult { + return evaluate(ResultMode.OPTIONAL, name) { a() } } /** @@ -316,7 +316,7 @@ open class Prov protected constructor( // which is the case if the result was not part of another subtask but created and returned by the lambda itself. // Success results do not need to be added here as they don't change the overall success evaluation, // whereas the failure results may have a useful error message, which should be in the output. - if (!resultOfTaskLambda.success && (resultOfTaskLambda != internalResults.last().provResult)) { + if (!resultOfTaskLambda.success && (resultIndex < internalResults.size - 1) && (resultOfTaskLambda != internalResults[resultIndex + 1].provResult)) { internalResults.add(ResultLine(level + 1, "<>", resultOfTaskLambda)) } @@ -330,8 +330,12 @@ open class Prov protected constructor( } + /** + * Returns true if the task at the specified index has no subtasks. + * I.e. if the task is the last one or if level of the next task is the same or less (which means same level or "higher" in the tree) + */ private fun internalResultIsLeaf(resultIndex: Int): Boolean { - return !(resultIndex < internalResults.size - 1 && internalResults[resultIndex + 1].level > internalResults[resultIndex].level) + return (resultIndex >= internalResults.size - 1 || internalResults[resultIndex].level >= internalResults[resultIndex + 1].level) } diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Utils.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Utils.kt index 0087aa5..98fa90a 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Utils.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/core/Utils.kt @@ -20,7 +20,7 @@ internal fun getCallingMethodName(): String? { val offsetVal = 1 val exclude = arrayOf("task", "task\$default", "taskWithResult\$default", "taskWithResult", "def", "def\$default", "record", "invoke", "invoke0", "evaluate", "evaluate\$default", ) // suffixes are also ignored as method names but will be added as suffix in the evaluation results - val suffixes = arrayOf("optional", "requireAll", "requireLast", "inContainer") + val suffixes = arrayOf("optional", "optional\$default", "requireAll", "requireLast", "requireLast\$default", "inContainer") var suffix = "" val callingFrame = Thread.currentThread().stackTrace @@ -30,7 +30,7 @@ internal fun getCallingMethodName(): String? { var inc = 0 while ((method in exclude) or (method in suffixes)) { if (method in suffixes && suffix == "") { - suffix = method + suffix = method.split("$")[0] } inc++ method = callingFrame[i + offsetVal + inc].methodName diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/core/ProvTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/core/ProvTest.kt index d78ed51..cd469d1 100644 --- a/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/core/ProvTest.kt +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/core/ProvTest.kt @@ -646,5 +646,84 @@ internal class ProvTest { } + // method to be used in the next test + fun Prov.testMethodForOutputTest_with_returned_results() = taskWithResult { + + taskWithResult(name = "sub1") { + taskWithResult("sub2a") { + ProvResult(true) + } + taskWithResult("sub2b") { + ProvResult(false, err = "error msg A for sub2b should be shown as result of sub2b") + } + optional("sub2c-optional") { + taskWithResult("sub3a-taskWithResult") { + addResultToEval(ProvResult(false, err = "returned-result - error msg B should be once in output - in addResultToEval")) + } + } + requireLast("sub2d-requireLast") { + taskWithResult("sub3b-taskWithResult without error message") { + ProvResult(false) // no error message + } + } + task("sub2e-task") { + addResultToEval(ProvResult(true)) + ProvResult(false, err = "error should NOT be in output as results of task (not taskWithResult) are ignored") + } + taskWithResult("sub2f-taskWithResult") { + ProvResult(false, err = "returned-result - error msg C should be once in output - at the end of sub3taskWithResult ") + } + ProvResult(false, err = "returned-result - error msg D should be once in output - at the end of sub1 ") + } + } + + @Test + @NonCi + fun prov_prints_correct_output_for_returned_results() { + + // given + setRootLoggingLevel(Level.OFF) + + val outContent = ByteArrayOutputStream() + val errContent = ByteArrayOutputStream() + val originalOut = System.out + val originalErr = System.err + + System.setOut(PrintStream(outContent)) + System.setErr(PrintStream(errContent)) + + // when + Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE) + .testMethodForOutputTest_with_returned_results() + + // then + System.setOut(originalOut) + System.setErr(originalErr) + + println(outContent.toString()) + + val expectedOutput = + "============================================== SUMMARY (test instance with no progress info) =============================================\n" + + "> \u001B[91mFAILED\u001B[0m -- testMethodForOutputTest_with_returned_results \n" + + "---> \u001B[91mFAILED\u001B[0m -- sub1 \n" + + "------> \u001B[92mSuccess\u001B[0m -- sub2a \n" + + "------> \u001B[91mFAILED\u001B[0m -- sub2b -- Error: error msg A for sub2b should be shown as result of sub2b\n" + + "------> \u001B[92mSuccess\u001B[0m -- sub2c-optional \n" + + "---------> \u001B[93mFAILED\u001B[0m -- sub3a-taskWithResult \n" + + "------------> \u001B[93mFAILED\u001B[0m -- addResultToEval -- Error: returned-result - error msg B should be once in output - in addResultToEval\n" + + "------> \u001B[91mFAILED\u001B[0m -- sub2d-requireLast \n" + + "---------> \u001B[91mFAILED\u001B[0m -- sub3b-taskWithResult without error message \n" + + "------> \u001B[92mSuccess\u001B[0m -- sub2e-task \n" + + "---------> \u001B[92mSuccess\u001B[0m -- addResultToEval \n" + + "------> \u001B[91mFAILED\u001B[0m -- sub2f-taskWithResult -- Error: returned-result - error msg C should be once in output - at the end of sub3taskWithResult \n" + + "------> \u001B[91mFAILED\u001B[0m -- <> -- Error: returned-result - error msg D should be once in output - at the end of sub1 \n" + + "----------------------------------------------------------------------------------------------------\n" + + "Overall > \u001B[91mFAILED\u001B[0m \n" + + "============================================ SUMMARY END ===========================================\n" + + "\n" + + assertEquals(expectedOutput, outContent.toString().replace("\r", "")) + } + }