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
@@ -92,6 +92,17 @@ fun fail(message: String? = null): Nothing {
asserter.fail(message) 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. */ /** Asserts that given function [block] returns the given [expected] value. */
fun <@OnlyInputTypes T> expect(expected: T, block: () -> T) { fun <@OnlyInputTypes T> expect(expected: T, block: () -> T) {
assertEquals(expected, block()) 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 = inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, message: String?, block: () -> Unit): T =
checkResultIsFailure(exceptionClass, message, runCatching(block)) 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 * Abstracts the logic for performing assertions. Specific implementations of [Asserter] can use JUnit
@@ -186,6 +199,15 @@ interface Asserter {
*/ */
fun fail(message: String?): Nothing 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`. * Asserts that the specified value is `true`.
* *
@@ -15,6 +15,11 @@ object DefaultAsserter : Asserter {
else else
throw AssertionError(message) 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", @Deprecated("DefaultAsserter is an object now, constructor call is not required anymore",
@@ -148,7 +148,7 @@ class BasicAssertionsTest {
@Test() @Test()
fun testAssertNotNullFails() { fun testAssertNotNullFails() {
checkFailedAssertion { assertNotNull(null) } checkFailedAssertion { assertNotNull<Any>(null) }
} }
@Test @Test
@@ -178,7 +178,18 @@ class BasicAssertionsTest {
@Test() @Test()
fun testFail() { 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 @Test
@@ -193,8 +204,8 @@ class BasicAssertionsTest {
} }
private fun checkFailedAssertion(assertion: () -> Unit) { private fun checkFailedAssertion(assertion: () -> Unit): AssertionError {
assertFailsWith<AssertionError> { withDefaultAsserter(assertion) } return assertFailsWith<AssertionError> { withDefaultAsserter(assertion) }
} }
private fun withDefaultAsserter(block: () -> Unit) { private fun withDefaultAsserter(block: () -> Unit) {
@@ -57,7 +57,7 @@ internal object DefaultJsAsserter : Asserter {
override fun assertTrue(lazyMessage: () -> String?, actual: Boolean) { override fun assertTrue(lazyMessage: () -> String?, actual: Boolean) {
if (!actual) { if (!actual) {
failWithMessage(lazyMessage) failWithMessage(lazyMessage, null)
} else { } else {
invokeHook(true, lazyMessage) invokeHook(true, lazyMessage)
} }
@@ -68,16 +68,18 @@ internal object DefaultJsAsserter : Asserter {
} }
override fun fail(message: String?): Nothing { 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() val message = lazyMessage()
invokeHook(false) { message } invokeHook(false) { message }
if (message == null) throw AssertionErrorWithCause(message, cause)
throw AssertionError()
else
throw AssertionError(message)
} }
private fun invokeHook(result: Boolean, lazyMessage: () -> String?) { private fun invokeHook(result: Boolean, lazyMessage: () -> String?) {
@@ -17,6 +17,11 @@ actual fun todo(block: () -> Unit) {
println("TODO at " + block) 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 @PublishedApi
internal actual fun <T : Throwable> checkResultIsFailure(exceptionClass: KClass<T>, message: String?, blockResult: Result<Unit>): T { internal actual fun <T : Throwable> checkResultIsFailure(exceptionClass: KClass<T>, message: String?, blockResult: Result<Unit>): T {
@@ -57,4 +57,16 @@ object JUnitAsserter : Asserter {
// should not get here // should not get here
throw AssertionError(message) 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)
}
} }
@@ -53,8 +53,15 @@ object JUnit5Asserter : Asserter {
} }
override fun fail(message: String?): Nothing { override fun fail(message: String?): Nothing {
Assertions.fail<Any>(message) Assertions.fail<Any?>(message)
// should not get here // should not get here
throw AssertionError(message) 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)
}
} }
@@ -72,3 +72,10 @@ actual inline fun todo(@Suppress("UNUSED_PARAMETER") block: () -> Unit) {
@InlineOnly @InlineOnly
inline fun currentStackTrace() = @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (java.lang.Exception() as java.lang.Throwable).stackTrace 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
}
@@ -57,4 +57,11 @@ object TestNGAsserter : Asserter {
// should not get here // should not get here
throw AssertionError(message) 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)
}
} }
@@ -80,6 +80,8 @@ public actual open class AssertionError : Error {
public actual constructor() : super() public actual constructor() : super()
public constructor(message: String?) : super(message) public constructor(message: String?) : super(message)
public actual constructor(message: Any?) : super(message?.toString(), message as? Throwable) 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 { public actual open class NoSuchElementException : RuntimeException {
@@ -80,7 +80,9 @@ public actual open class ClassCastException actual constructor(message: String?)
actual constructor() : this(null) 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) actual constructor() : this(null)
constructor(message: String?) : this(message, null) constructor(message: String?) : this(message, null)
actual constructor(message: Any?) : this(message.toString(), message as? Throwable) actual constructor(message: Any?) : this(message.toString(), message as? Throwable)