Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline lambdas passed to assertDoesNotThrows to support suspending functions #2684

Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ inline fun <reified T : Throwable> assertThrows(noinline message: () -> String,
}, Supplier(message))
}

@PublishedApi
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Again, possibly dumb question here, but why do we need this method to be inline?

Choose a reason for hiding this comment

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

Also, could you please move this method below the other ones calling it? :)

Copy link
Contributor Author

@williamboman williamboman Aug 13, 2021

Choose a reason for hiding this comment

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

Again, possibly dumb question here, but why do we need this method to be inline?

Because this function is ultimately the one responsible for calling the provided executable. We want to run the executable in the same context as the callsite of assertDoesNotThrow (so that it can suspend, etc.), so we need to make sure the full call stack from assertDoesNotThrow until here uses inlined functions. If we were to remove the inline modifier on this function, we'll get a compiler error in all of the assertDoesNotThrow functions saying that we're violating the inlineability (I totally just made that word up) of the executable argument, and that we need to change the function signature to assertDoesNotThrow(noinline executable: () -> R). Doing so breaks the tests that now calls a suspending function, because the callback is no longer inlined.

edit: This is at least my understanding

Choose a reason for hiding this comment

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

I see! Thanks for the explanation 😄

internal inline fun <R> getThrowingSupplier(executable: () -> R): ThrowingSupplier<R> = try {
val result = executable()
ThrowingSupplier { result }
} catch (throwable: Throwable) {
ThrowingSupplier { throw throwable }
}

/**
* Example usage:
* ```kotlin
Expand All @@ -158,8 +166,8 @@ inline fun <reified T : Throwable> assertThrows(noinline message: () -> String,
* @param R the result type of the [executable]
*/
@API(status = EXPERIMENTAL, since = "5.5")
fun <R> assertDoesNotThrow(executable: () -> R): R =
Assertions.assertDoesNotThrow(ThrowingSupplier(executable))
inline fun <R> assertDoesNotThrow(executable: () -> R): R =
Assertions.assertDoesNotThrow(getThrowingSupplier(executable))

/**
* Example usage:
Expand All @@ -172,7 +180,7 @@ fun <R> assertDoesNotThrow(executable: () -> R): R =
* @param R the result type of the [executable]
*/
@API(status = EXPERIMENTAL, since = "5.5")
fun <R> assertDoesNotThrow(message: String, executable: () -> R): R =
inline fun <R> assertDoesNotThrow(message: String, executable: () -> R): R =
assertDoesNotThrow({ message }, executable)

/**
Expand All @@ -186,8 +194,11 @@ fun <R> assertDoesNotThrow(message: String, executable: () -> R): R =
* @param R the result type of the [executable]
*/
@API(status = EXPERIMENTAL, since = "5.5")
fun <R> assertDoesNotThrow(message: () -> String, executable: () -> R): R =
Assertions.assertDoesNotThrow(ThrowingSupplier(executable), Supplier(message))
inline fun <R> assertDoesNotThrow(noinline message: () -> String, executable: () -> R): R =
Assertions.assertDoesNotThrow(
getThrowingSupplier(executable),
Supplier(message)
)

/**
* Example usage:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,21 @@ class KotlinAssertionsTests {
}

@Test
fun `expected context exception testing`() = runBlocking<Unit> {
fun `assertThrows with coroutines`() = runBlocking<Unit> {
assertThrows<AssertionError>("Should fail async") {
suspend { fail("Should fail async") }()
}
}

@Test
fun `assertDoesNotThrow with coroutines`() = runBlocking {
val result = assertDoesNotThrow {
suspend { "Result" }()

Choose a reason for hiding this comment

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

We might want to have another test with a suspending function that actually throws an exception (thus making the test fail), since we want to make sure that the assertion is actually waiting for the "throwing"

Choose a reason for hiding this comment

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

Just noticed the test factory below, you could even add one dynamic test in each dynamicContainer for this new suspending function use case :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! d48eeb4

}

assertEquals("Result", result)
}

@TestFactory
fun assertDoesNotThrow(): Stream<out DynamicNode> = Stream.of(
dynamicContainer("succeeds when no exception thrown", Stream.of(
Expand Down