Skip to content

Commit

Permalink
WIP ParameterizeFailedError
Browse files Browse the repository at this point in the history
  • Loading branch information
BenWoodworth committed Nov 1, 2023
1 parent 09abc7c commit 8da8bed
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 89 deletions.
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,33 @@ fun contains_with_the_substring_present_should_be_true() = parameterize {
val suffix by parameterOf("-suffix", "")

val string = "$prefix$substring$suffix"
assertTrue(string.contains(substring))
assertTrue(string.contains(substring), "\"$string\".contains(\"$substring\")")
}
```

If the test fails, the cause will be wrapped into an `Error` detailing the <ins>*used*</ins> parameters with their
arguments <ins>*and parameter names*</ins>:

```java
com.benwoodworth.parameterize.ParameterizeFailedError: Failed with arguments:
prefix = prefix-
suffix = -suffix
Caused by: org.opentest4j.AssertionFailedError: Expected value to be true.
at org.junit.jupiter.api.Assertions.fail(Assertions.java:109)
at kotlin.test.junit5.JUnit5Asserter.fail(JUnitSupport.kt:56)
at kotlin.test.AssertionsKt__AssertionsKt.assertTrue(Assertions.kt:44)
at ContainsSpec$contains_with_the_substring_present_should_be_true$1.invoke(ContainsSpec.kt:7)
at ContainsSpec$contains_with_the_substring_present_should_be_true$1.invoke(ContainsSpec.kt:1)
at ContainsSpec.contains_with_the_substring_present_should_be_true(ContainsSpec.kt:1)
com.benwoodworth.parameterize.ParameterizeFailedError: Failed 2/4 cases
AssertionFailedError: "prefix-substring-suffix".contains("substring")
AssertionFailedError: "prefix-substring".contains("substring")
Suppressed: com.benwoodworth.parameterize.Failure: Failed with arguments:
prefix = prefix-
suffix = -suffix
Caused by: org.opentest4j.AssertionFailedError: "prefix-substring-suffix".contains("substring")
at kotlin.test.AssertionsKt.assertTrue(Unknown Source)
at ContainsSpec$contains_with_the_substring_present_should_be_true$1.invoke(ContainsSpec.kt:13)
at ContainsSpec$contains_with_the_substring_present_should_be_true$1.invoke(ContainsSpec.kt:7)
at com.benwoodworth.parameterize.ParameterizeKt.parameterize(Parameterize.kt:91)
Suppressed: com.benwoodworth.parameterize.Failure: Failed with arguments:
prefix = prefix-
suffix =
Caused by: org.opentest4j.AssertionFailedError: "prefix-substring".contains("substring")
at kotlin.test.AssertionsKt.assertTrue(Unknown Source)
at ContainsSpec$contains_with_the_substring_present_should_be_true$1.invoke(ContainsSpec.kt:13)
at ContainsSpec$contains_with_the_substring_present_should_be_true$1.invoke(ContainsSpec.kt:7)
at com.benwoodworth.parameterize.ParameterizeKt.parameterize(Parameterize.kt:91)
```

Parameters are also designed to be flexible, depend on other parameters, be called conditionally, or even used a loop to
Expand Down
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/ParameterizeConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,6 @@ public class ParameterizeConfiguration private constructor(
public val recordedFailures: List<ParameterizeFailure>
) {
public operator fun ParameterizeFailedError.Companion.invoke(): ParameterizeFailedError =
ParameterizeFailedError(recordedFailures, iterationCount, failureCount)
ParameterizeFailedError(recordedFailures, failureCount, iterationCount, coveredAllCases)
}
}
87 changes: 72 additions & 15 deletions src/commonMain/kotlin/ParameterizeFailedError.kt
Original file line number Diff line number Diff line change
@@ -1,37 +1,94 @@
package com.benwoodworth.parameterize

/**
* Thrown from a parameterize [throw handler][ParameterizeConfiguration.onFailure] to indicate that [parameterize]
* failed with the given [arguments], and the thrown failure as the [cause]. TODO
* Thrown to indicate that [parameterize] has completed with failures.
*
* The [message] summarizes the number of failures and total iterations. If the total is followed by a `+`, then
* [parameterize] has not [coveredAllCases][ParameterizeConfiguration.OnCompleteScope.coveredAllCases], and completed
* early.
*
* The [suppressedExceptions] include [recordedFailures][ParameterizeConfiguration.OnCompleteScope.recordedFailures]
* from the [onComplete][ParameterizeConfiguration.onComplete] handler, with each being decorated with a message to
* include a list of the [arguments][ParameterizeFailure.arguments] that caused it.
*
* Can only be constructed from [ParameterizeConfiguration.onComplete].
*/
public class ParameterizeFailedError internal constructor(
internal val recordedFailures: List<ParameterizeFailure>,
internal val iterationCount: Long,
internal val failureCount: Long,
) : Error() {
private val recordedFailures: List<ParameterizeFailure>,
private val failureCount: Long,
private val iterationCount: Long,
private val coveredAllCases: Boolean
) : AssertionError() {
// TODO: Use context receiver instead of companion + pseudo constructor
public companion object;

init {
clearStackTrace()
if (recordedFailures.isNotEmpty()) {
clearStackTrace()
}

recordedFailures.forEach { failure ->
addSuppressed(Failure(failure))
}
}

private val arguments = recordedFailures.firstOrNull()?.arguments
override val message: String = buildString {
append("Failed ")
append(failureCount)
append('/')
append(iterationCount)

if (!coveredAllCases) {
append('+')
}

append(" cases")

if (recordedFailures.isNotEmpty()) {
recordedFailures.forEach { failure ->
append("\n\t")
append(failure.failure::class.simpleName)
append(": ")

override val message: String = when (arguments?.size) {
null -> "No recorded failures"
val message = failure.failure.message?.trim()
if (message.isNullOrBlank()) {
append("<no message>")
} else {
val firstNewLine = message.indexOfFirst { it == '\n' || it == '\r' }

if (firstNewLine == -1) {
append(message)
} else {
append(message, 0, firstNewLine)
append(" ...")
}
}
}

if (recordedFailures.size < failureCount) {
append("\n\t...")
}
}
}
}

private class Failure(
failure: ParameterizeFailure,
) : AssertionError(failure.failure) {
init {
clearStackTrace()
}

override val message: String = when (failure.arguments.size) {
0 -> "Failed with no arguments"

1 -> arguments.single().let { argument ->
"Failed with argument: $argument"
1 -> failure.arguments.single().let { argument ->
"Failed with argument:\n\t\t$argument"
}

else -> arguments.joinToString(
prefix = "Failed with arguments:\n\t",
separator = "\n\t"
else -> failure.arguments.joinToString(
prefix = "Failed with arguments:\n\t\t",
separator = "\n\t\t"
)
}
}
Expand Down
44 changes: 22 additions & 22 deletions src/commonTest/kotlin/ParameterizeConfigurationOnCompleteSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -209,26 +209,26 @@ class ParameterizeConfigurationOnCompleteSpec {
}
}

@Test
fun error_constructor_should_build_error_with_correct_values() {
parameterize(
onFailure = {
recordFailure = iterationCount % 3 == 0L
},
onComplete = {
val error = ParameterizeFailedError()

assertEquals(recordedFailures, error.recordedFailures, error::recordedFailures.name)
assertEquals(iterationCount, error.iterationCount, error::iterationCount.name)
assertEquals(failureCount, error.failureCount, error::failureCount.name)
}
) {
val iterations = 0..100
val iteration by parameter(iterations)

if (iteration % 2 == 0 || iteration % 3 == 0) {
fail("$iteration")
}
}
}
// @Test
// fun error_constructor_should_build_error_with_correct_values() {
// parameterize(
// onFailure = {
// recordFailure = iterationCount % 3 == 0L
// },
// onComplete = {
// val error = ParameterizeFailedError()
//
// assertEquals(recordedFailures, error.recordedFailures, error::recordedFailures.name)
// assertEquals(iterationCount, error.iterationCount, error::iterationCount.name)
// assertEquals(failureCount, error.failureCount, error::failureCount.name)
// }
// ) {
// val iterations = 0..100
// val iteration by parameter(iterations)
//
// if (iteration % 2 == 0 || iteration % 3 == 0) {
// fail("$iteration")
// }
// }
// }
}
12 changes: 6 additions & 6 deletions src/commonTest/kotlin/ParameterizeConfigurationSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ class ParameterizeConfigurationSpec {
}
}

val expectedRecordedFailures = iterations
.mapNotNull { it.exceptionOrNull() }
.take(10)

val actualIterations = mutableListOf<Result<Unit>>()

// Test assumes this is the default type thrown failures
Expand All @@ -175,12 +179,8 @@ class ParameterizeConfigurationSpec {
}
}

val expectedRecordedFailures = iterations
.mapNotNull { it.exceptionOrNull() }
.take(10)

val actualRecordedFailures = failure.recordedFailures
.map { it.failure }
val actualRecordedFailures = failure.suppressedExceptions
.map { augmentedFailure -> augmentedFailure.cause }

assertEquals(iterations, actualIterations, "Should not break")
assertEquals(expectedRecordedFailures, actualRecordedFailures, "Should record first 10 failures")
Expand Down
Loading

0 comments on commit 8da8bed

Please sign in to comment.