Introduce 'fail' method to throw AssertionError with cause

#KT-37804
This commit is contained in:
Ilya Gorbunov
2020-01-26 00:02:43 +03:00
parent d2ff98fcb1
commit 2bb36899da
11 changed files with 95 additions and 13 deletions

View File

@@ -92,6 +92,17 @@ fun fail(message: String? = null): Nothing {
asserter.fail(message)
}
/**
* Marks a test as having failed if this point in the execution path is reached, with an optional [message]
* and [cause] exception.
*
* The [cause] exception is set as the root cause of the test failure.
*/
@SinceKotlin("1.4")
fun fail(message: String? = null, cause: Throwable? = null): Nothing {
asserter.fail(message, cause)
}
/** Asserts that given function [block] returns the given [expected] value. */
fun <@OnlyInputTypes T> expect(expected: T, block: () -> T) {
assertEquals(expected, block())
@@ -173,6 +184,8 @@ inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, block: ()
inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, message: String?, block: () -> Unit): T =
checkResultIsFailure(exceptionClass, message, runCatching(block))
/** Platform-specific construction of AssertionError with cause */
internal expect fun AssertionErrorWithCause(message: String?, cause: Throwable?): AssertionError
/**
* Abstracts the logic for performing assertions. Specific implementations of [Asserter] can use JUnit
@@ -186,6 +199,15 @@ interface Asserter {
*/
fun fail(message: String?): Nothing
/**
* Fails the current test with the specified message and cause exception.
*
* @param message the message to report.
* @param cause the exception to set as the root cause of the reported failure.
*/
@SinceKotlin("1.4")
fun fail(message: String?, cause: Throwable?): Nothing
/**
* Asserts that the specified value is `true`.
*

View File

@@ -15,6 +15,11 @@ object DefaultAsserter : Asserter {
else
throw AssertionError(message)
}
@SinceKotlin("1.4")
override fun fail(message: String?, cause: Throwable?): Nothing {
throw AssertionErrorWithCause(message, cause)
}
}
@Deprecated("DefaultAsserter is an object now, constructor call is not required anymore",

View File

@@ -148,7 +148,7 @@ class BasicAssertionsTest {
@Test()
fun testAssertNotNullFails() {
checkFailedAssertion { assertNotNull(null) }
checkFailedAssertion { assertNotNull<Any>(null) }
}
@Test
@@ -178,7 +178,18 @@ class BasicAssertionsTest {
@Test()
fun testFail() {
checkFailedAssertion { fail("should fail") }
val message = "should fail"
val actual = checkFailedAssertion { fail(message) }
assertEquals(message, actual.message)
}
@Test
fun testFailWithCause() {
val message = "should fail due to"
val cause = IllegalStateException()
val actual = checkFailedAssertion { fail(message, cause) }
assertEquals(message, actual.message)
assertSame(cause, actual.cause)
}
@Test
@@ -193,8 +204,8 @@ class BasicAssertionsTest {
}
private fun checkFailedAssertion(assertion: () -> Unit) {
assertFailsWith<AssertionError> { withDefaultAsserter(assertion) }
private fun checkFailedAssertion(assertion: () -> Unit): AssertionError {
return assertFailsWith<AssertionError> { withDefaultAsserter(assertion) }
}
private fun withDefaultAsserter(block: () -> Unit) {

View File

@@ -57,7 +57,7 @@ internal object DefaultJsAsserter : Asserter {
override fun assertTrue(lazyMessage: () -> String?, actual: Boolean) {
if (!actual) {
failWithMessage(lazyMessage)
failWithMessage(lazyMessage, null)
} else {
invokeHook(true, lazyMessage)
}
@@ -68,16 +68,18 @@ internal object DefaultJsAsserter : Asserter {
}
override fun fail(message: String?): Nothing {
failWithMessage { message }
fail(message, null)
}
private fun failWithMessage(lazyMessage: () -> String?): Nothing {
@SinceKotlin("1.4")
override fun fail(message: String?, cause: Throwable?): Nothing {
failWithMessage({ message }, cause)
}
private inline fun failWithMessage(lazyMessage: () -> String?, cause: Throwable?): Nothing {
val message = lazyMessage()
invokeHook(false) { message }
if (message == null)
throw AssertionError()
else
throw AssertionError(message)
throw AssertionErrorWithCause(message, cause)
}
private fun invokeHook(result: Boolean, lazyMessage: () -> String?) {

View File

@@ -17,6 +17,11 @@ actual fun todo(block: () -> Unit) {
println("TODO at " + block)
}
/** Platform-specific construction of AssertionError with cause */
@Suppress("NOTHING_TO_INLINE")
internal actual inline fun AssertionErrorWithCause(message: String?, cause: Throwable?): AssertionError =
AssertionError(message, cause)
@PublishedApi
internal actual fun <T : Throwable> checkResultIsFailure(exceptionClass: KClass<T>, message: String?, blockResult: Result<Unit>): T {

View File

@@ -57,4 +57,16 @@ object JUnitAsserter : Asserter {
// should not get here
throw AssertionError(message)
}
@SinceKotlin("1.4")
override fun fail(message: String?, cause: Throwable?): Nothing {
try {
Assert.fail(message)
} catch (e: AssertionError) {
e.initCause(cause)
throw e
}
// should not get here
throw AssertionError(message).initCause(cause)
}
}

View File

@@ -53,8 +53,15 @@ object JUnit5Asserter : Asserter {
}
override fun fail(message: String?): Nothing {
Assertions.fail<Any>(message)
Assertions.fail<Any?>(message)
// should not get here
throw AssertionError(message)
}
@SinceKotlin("1.4")
override fun fail(message: String?, cause: Throwable?): Nothing {
Assertions.fail<Any?>(message, cause)
// should not get here
throw AssertionError(message, cause)
}
}

View File

@@ -72,3 +72,10 @@ actual inline fun todo(@Suppress("UNUSED_PARAMETER") block: () -> Unit) {
@InlineOnly
inline fun currentStackTrace() = @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (java.lang.Exception() as java.lang.Throwable).stackTrace
/** Platform-specific construction of AssertionError with cause */
internal actual fun AssertionErrorWithCause(message: String?, cause: Throwable?): AssertionError {
val assertionError = if (message == null) AssertionError() else AssertionError(message)
assertionError.initCause(cause)
return assertionError
}

View File

@@ -57,4 +57,11 @@ object TestNGAsserter : Asserter {
// should not get here
throw AssertionError(message)
}
@SinceKotlin("1.4")
override fun fail(message: String?, cause: Throwable?): Nothing {
Assert.fail(message, cause)
// should not get here
throw AssertionError(message).initCause(cause)
}
}

View File

@@ -80,6 +80,8 @@ public actual open class AssertionError : Error {
public actual constructor() : super()
public constructor(message: String?) : super(message)
public actual constructor(message: Any?) : super(message?.toString(), message as? Throwable)
@SinceKotlin("1.4")
public constructor(message: String?, cause: Throwable?) : super(message, cause)
}
public actual open class NoSuchElementException : RuntimeException {

View File

@@ -80,7 +80,9 @@ public actual open class ClassCastException actual constructor(message: String?)
actual constructor() : this(null)
}
public actual open class AssertionError private constructor(message: String?, cause: Throwable?) : Error(message, cause) {
public actual open class AssertionError
@SinceKotlin("1.4")
constructor(message: String?, cause: Throwable?) : Error(message, cause) {
actual constructor() : this(null)
constructor(message: String?) : this(message, null)
actual constructor(message: Any?) : this(message.toString(), message as? Throwable)