From e3b3ac38eea3ffe230a91c706e98ad2836b9406b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 7 Apr 2023 17:55:49 +0200 Subject: [PATCH 01/17] Added contracts for kotlin assertions Added assertNull and assertNotNull methods with contracts. Added contracts for assertThrows and assertDoesNotThrow methods. assertInstanceOf can be implemented only with kotlin 1.4, because refined generics [are not supported](https://youtrack.jetbrains.com/issue/KT-28298) in contracts for kotlin 1.3 yet. Issue: #1866 --- .../junit-jupiter-api.gradle.kts | 8 + .../org/junit/jupiter/api/Assertions.kt | 238 ++++++++++++++++-- .../jupiter/api/KotlinAssertionsTests.kt | 40 +++ 3 files changed, 266 insertions(+), 20 deletions(-) diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 7dd03a78f4e9..8e43b97f89be 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -31,3 +31,11 @@ tasks { } } } + +kotlin { + sourceSets { + main { + languageSettings.optIn("kotlin.contracts.ExperimentalContracts") + } + } +} diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index d0f5af1e218b..ab04c4cc1d08 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -19,6 +19,9 @@ import org.junit.jupiter.api.function.ThrowingSupplier import java.time.Duration import java.util.function.Supplier import java.util.stream.Stream +import kotlin.contracts.InvocationKind.AT_MOST_ONCE +import kotlin.contracts.InvocationKind.EXACTLY_ONCE +import kotlin.contracts.contract /** * @see Assertions.fail @@ -86,6 +89,139 @@ fun assertAll(vararg executables: () -> Unit) = fun assertAll(heading: String?, vararg executables: () -> Unit) = assertAll(heading, executables.toList().stream()) +/** + * Example usage: + * ```kotlin + * val string: String? = ... + * + * assertNull(string) + * + * // compiler won't allow even safe calls, since the string is always null + * // string?.isNotEmpty() + * ``` + * @see Assertions.assertNull + */ +@API(since = "5.10", status = STABLE) +fun assertNull(actual: Any?) { + contract { + returns() implies (actual == null) + } + + Assertions.assertNull(actual) +} + +/** + * Example usage: + * ```kotlin + * val string: String? = ... + * + * assertNull(string, "Should be nullable") + * + * // compiler won't allow even safe calls, since the string is always null + * // string?.isNotEmpty() + * ``` + * @see Assertions.assertNull + */ +@API(since = "5.10", status = STABLE) +fun assertNull(actual: Any?, message: String) { + contract { + returns() implies (actual == null) + } + + Assertions.assertNull(actual, message) +} + +/** + * Example usage: + * ```kotlin + * val string: String? = ... + * + * assertNull(string) { "Should be nullable" } + * + * // compiler won't allow even safe calls, since the string is always null + * // string?.isNotEmpty() + * ``` + * @see Assertions.assertNull + */ +@API(since = "5.10", status = STABLE) +fun assertNull(actual: Any?, messageSupplier: () -> String) { + contract { + returns() implies (actual == null) + + callsInPlace(messageSupplier, AT_MOST_ONCE) + } + + Assertions.assertNull(actual, messageSupplier) +} + +/** + * Example usage: + * ```kotlin + * val string: String? = ... + * + * assertNotNull(string) + * + * // compiler smart casts nullableString to a non-nullable object + * assertTrue(string.isNotEmpty()) + * ``` + * @see Assertions.assertNotNull + */ +@API(since = "5.10", status = STABLE) +fun assertNotNull(actual: T?): T { + contract { + returns() implies (actual != null) + } + + Assertions.assertNotNull(actual) + return actual!! +} + +/** + * Example usage: + * ```kotlin + * val string: String? = ... + * + * assertNotNull(string, "Should be non-nullable") + * + * // compiler smart casts nullableString to a non-nullable object + * assertTrue(string.isNotEmpty()) + * ``` + * @see Assertions.assertNotNull + */ +@API(since = "5.10", status = STABLE) +fun assertNotNull(actual: T?, message: String): T { + contract { + returns() implies (actual != null) + } + + Assertions.assertNotNull(actual, message) + return actual!! +} + +/** + * Example usage: + * ```kotlin + * val string: String? = ... + * + * assertNotNull(string) { "Should be non-nullable" } + * + * // compiler smart casts nullableString to a non-nullable object + * assertTrue(string.isNotEmpty()) + * ``` + * @see Assertions.assertNotNull + */ +@API(since = "5.10", status = STABLE) +fun assertNotNull(actual: T?, messageSupplier: () -> String): T { + contract { + returns() implies (actual != null) + + callsInPlace(messageSupplier, AT_MOST_ONCE) + } + + Assertions.assertNotNull(actual, messageSupplier) + return actual!! +} + /** * Example usage: * ```kotlin @@ -97,6 +233,10 @@ fun assertAll(heading: String?, vararg executables: () -> Unit) = * @see Assertions.assertThrows */ inline fun assertThrows(executable: () -> Unit): T { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + val throwable: Throwable? = try { executable() } catch (caught: Throwable) { @@ -120,8 +260,13 @@ inline fun assertThrows(executable: () -> Unit): T { * ``` * @see Assertions.assertThrows */ -inline fun assertThrows(message: String, executable: () -> Unit): T = - assertThrows({ message }, executable) +inline fun assertThrows(message: String, executable: () -> Unit): T { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return assertThrows({ message }, executable) +} /** * Example usage: @@ -134,6 +279,11 @@ inline fun assertThrows(message: String, executable: () * @see Assertions.assertThrows */ inline fun assertThrows(noinline message: () -> String, executable: () -> Unit): T { + contract { + callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(message, AT_MOST_ONCE) + } + val throwable: Throwable? = try { executable() } catch (caught: Throwable) { @@ -162,8 +312,13 @@ inline fun assertThrows(noinline message: () -> String, * @param R the result type of the [executable] */ @API(status = EXPERIMENTAL, since = "5.5") -inline fun assertDoesNotThrow(executable: () -> R): R = - Assertions.assertDoesNotThrow(evaluateAndWrap(executable)) +inline fun assertDoesNotThrow(executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertDoesNotThrow(evaluateAndWrap(executable)) +} /** * Example usage: @@ -176,8 +331,13 @@ inline fun assertDoesNotThrow(executable: () -> R): R = * @param R the result type of the [executable] */ @API(status = EXPERIMENTAL, since = "5.5") -inline fun assertDoesNotThrow(message: String, executable: () -> R): R = - assertDoesNotThrow({ message }, executable) +inline fun assertDoesNotThrow(message: String, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return assertDoesNotThrow({ message }, executable) +} /** * Example usage: @@ -190,11 +350,17 @@ inline fun assertDoesNotThrow(message: String, executable: () -> R): R = * @param R the result type of the [executable] */ @API(status = EXPERIMENTAL, since = "5.5") -inline fun assertDoesNotThrow(noinline message: () -> String, executable: () -> R): R = - Assertions.assertDoesNotThrow( +inline fun assertDoesNotThrow(noinline message: () -> String, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(message, AT_MOST_ONCE) + } + + return Assertions.assertDoesNotThrow( evaluateAndWrap(executable), Supplier(message) ) +} @PublishedApi internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier = try { @@ -215,8 +381,13 @@ internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier assertTimeout(timeout: Duration, executable: () -> R): R = - Assertions.assertTimeout(timeout, executable) +fun assertTimeout(timeout: Duration, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertTimeout(timeout, executable) +} /** * Example usage: @@ -229,8 +400,13 @@ fun assertTimeout(timeout: Duration, executable: () -> R): R = * @paramR the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeout(timeout: Duration, message: String, executable: () -> R): R = - Assertions.assertTimeout(timeout, executable, message) +fun assertTimeout(timeout: Duration, message: String, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertTimeout(timeout, executable, message) +} /** * Example usage: @@ -243,8 +419,14 @@ fun assertTimeout(timeout: Duration, message: String, executable: () -> R): * @paramR the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeout(timeout: Duration, message: () -> String, executable: () -> R): R = - Assertions.assertTimeout(timeout, executable, message) +fun assertTimeout(timeout: Duration, message: () -> String, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(message, AT_MOST_ONCE) + } + + return Assertions.assertTimeout(timeout, executable, message) +} /** * Example usage: @@ -257,8 +439,13 @@ fun assertTimeout(timeout: Duration, message: () -> String, executable: () - * @paramR the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R = - Assertions.assertTimeoutPreemptively(timeout, executable) +fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertTimeoutPreemptively(timeout, executable) +} /** * Example usage: @@ -271,8 +458,13 @@ fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R = * @paramR the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R = - Assertions.assertTimeoutPreemptively(timeout, executable, message) +fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertTimeoutPreemptively(timeout, executable, message) +} /** * Example usage: @@ -285,5 +477,11 @@ fun assertTimeoutPreemptively(timeout: Duration, message: String, executable * @paramR the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R = - Assertions.assertTimeoutPreemptively(timeout, executable, message) +fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(message, AT_MOST_ONCE) + } + + return Assertions.assertTimeoutPreemptively(timeout, executable, message) +} diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index 3386acf06b5d..582d9a4adbb1 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -211,6 +211,46 @@ class KotlinAssertionsTests { assertMessageStartsWith(error, assertionMessage) } + @Test + fun `assertNotNull with compiler smart cast`() { + val nullableString: String? = "string" + + assertNotNull(nullableString) + assertFalse(nullableString.isEmpty()) // smart cast to non nullable object + } + + @Test + fun `assertNull with compiler smart cast`() { + val nullableString: String? = null + + assertNull(nullableString) + // even safe call is not allowed, because compiler knows that string is always null + // nullableString?.isEmpty() + } + + @Test + fun `assertThrows with value initialization in lambda`() { + val value: String + + assertThrows { + value = "string" + Assertions.fail("message") + } + + assertEquals("string", value) + } + + @Test + fun `assertDoesNotThrow with value initialization in lambda`() { + val value: Int + + assertDoesNotThrow { + value = 10 + } + + assertEquals(10, value) + } + companion object { fun assertExpectedExceptionTypes( multipleFailuresError: MultipleFailuresError, From edbe73f5113963f841cd43e546394ddd81868bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Thu, 10 Aug 2023 09:11:31 +0200 Subject: [PATCH 02/17] Merge branch 'main' into main --- .../docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc index f428b0e5a636..b071e738f8d7 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc @@ -23,8 +23,7 @@ JUnit repository on GitHub. ==== New Features and Improvements -* ❓ - +* Introduced kotlin contracts for kotlin assertion methods [[release-notes-5.11.0-M1-junit-jupiter]] === JUnit Jupiter From 252cad2282dd3fac21ac7b07aad22985cdcf2b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sun, 23 Apr 2023 21:04:40 +0200 Subject: [PATCH 03/17] Mark new kotlin assetion methods as experimental Kotlin assertNull and assertNotNull methods are marked as experimental. Issue: #1866 --- .../main/kotlin/org/junit/jupiter/api/Assertions.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index ab04c4cc1d08..366a42beea52 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -101,7 +101,7 @@ fun assertAll(heading: String?, vararg executables: () -> Unit) = * ``` * @see Assertions.assertNull */ -@API(since = "5.10", status = STABLE) +@API(since = "5.10", status = EXPERIMENTAL) fun assertNull(actual: Any?) { contract { returns() implies (actual == null) @@ -122,7 +122,7 @@ fun assertNull(actual: Any?) { * ``` * @see Assertions.assertNull */ -@API(since = "5.10", status = STABLE) +@API(since = "5.10", status = EXPERIMENTAL) fun assertNull(actual: Any?, message: String) { contract { returns() implies (actual == null) @@ -143,7 +143,7 @@ fun assertNull(actual: Any?, message: String) { * ``` * @see Assertions.assertNull */ -@API(since = "5.10", status = STABLE) +@API(since = "5.10", status = EXPERIMENTAL) fun assertNull(actual: Any?, messageSupplier: () -> String) { contract { returns() implies (actual == null) @@ -166,7 +166,7 @@ fun assertNull(actual: Any?, messageSupplier: () -> String) { * ``` * @see Assertions.assertNotNull */ -@API(since = "5.10", status = STABLE) +@API(since = "5.10", status = EXPERIMENTAL) fun assertNotNull(actual: T?): T { contract { returns() implies (actual != null) @@ -188,7 +188,7 @@ fun assertNotNull(actual: T?): T { * ``` * @see Assertions.assertNotNull */ -@API(since = "5.10", status = STABLE) +@API(since = "5.10", status = EXPERIMENTAL) fun assertNotNull(actual: T?, message: String): T { contract { returns() implies (actual != null) @@ -210,7 +210,7 @@ fun assertNotNull(actual: T?, message: String): T { * ``` * @see Assertions.assertNotNull */ -@API(since = "5.10", status = STABLE) +@API(since = "5.10", status = EXPERIMENTAL) fun assertNotNull(actual: T?, messageSupplier: () -> String): T { contract { returns() implies (actual != null) From 1d5811b8652bf0e79a1d3a8f17e2ff7858996917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sat, 3 Jun 2023 15:10:54 +0200 Subject: [PATCH 04/17] Adjust assertThrows contract Lambda invoked in assertThrows is marked as being called UNKNOWN number of times, since contracts do nothing with exception suppression Issue: #1866 --- .../kotlin/org/junit/jupiter/api/Assertions.kt | 6 +++--- .../org/junit/jupiter/api/KotlinAssertionsTests.kt | 14 +------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 366a42beea52..8cfaf6319164 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -234,7 +234,7 @@ fun assertNotNull(actual: T?, messageSupplier: () -> String): T { */ inline fun assertThrows(executable: () -> Unit): T { contract { - callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(executable) } val throwable: Throwable? = try { @@ -262,7 +262,7 @@ inline fun assertThrows(executable: () -> Unit): T { */ inline fun assertThrows(message: String, executable: () -> Unit): T { contract { - callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(executable) } return assertThrows({ message }, executable) @@ -280,7 +280,7 @@ inline fun assertThrows(message: String, executable: () */ inline fun assertThrows(noinline message: () -> String, executable: () -> Unit): T { contract { - callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(executable) callsInPlace(message, AT_MOST_ONCE) } diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index 582d9a4adbb1..eedcaf6b10bd 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -216,7 +216,7 @@ class KotlinAssertionsTests { val nullableString: String? = "string" assertNotNull(nullableString) - assertFalse(nullableString.isEmpty()) // smart cast to non nullable object + assertFalse(nullableString.isEmpty()) // smart cast to a non nullable object } @Test @@ -228,18 +228,6 @@ class KotlinAssertionsTests { // nullableString?.isEmpty() } - @Test - fun `assertThrows with value initialization in lambda`() { - val value: String - - assertThrows { - value = "string" - Assertions.fail("message") - } - - assertEquals("string", value) - } - @Test fun `assertDoesNotThrow with value initialization in lambda`() { val value: Int From 19922d6aee2219ca568251f30787813206759410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sat, 3 Jun 2023 15:31:32 +0200 Subject: [PATCH 05/17] Add contract for fail Created another fail method with a non-nullable lambda parameter Issue: #1866 --- .../main/kotlin/org/junit/jupiter/api/Assertions.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 8cfaf6319164..3aa9c83bcbb7 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -29,6 +29,18 @@ import kotlin.contracts.contract fun fail(message: String?, throwable: Throwable? = null): Nothing = Assertions.fail(message, throwable) +/** + * @see Assertions.fail + */ +@JvmName("fail_nonNullableLambda") +fun fail(message: () -> String): Nothing { + contract { + callsInPlace(message) + } + + return Assertions.fail(message) +} + /** * @see Assertions.fail */ From eccb2598019753c3aa22760cc1c37b8382ea106c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 25 Aug 2023 14:51:50 +0200 Subject: [PATCH 06/17] Don't return a value from assertNotNull Because of kotlin smart casts, there is no need to return a non-nullable value from `assertNotNull` methods Issue: #1866 --- .../src/main/kotlin/org/junit/jupiter/api/Assertions.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 3aa9c83bcbb7..980b53bc0382 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -179,13 +179,12 @@ fun assertNull(actual: Any?, messageSupplier: () -> String) { * @see Assertions.assertNotNull */ @API(since = "5.10", status = EXPERIMENTAL) -fun assertNotNull(actual: T?): T { +fun assertNotNull(actual: Any?) { contract { returns() implies (actual != null) } Assertions.assertNotNull(actual) - return actual!! } /** @@ -201,13 +200,12 @@ fun assertNotNull(actual: T?): T { * @see Assertions.assertNotNull */ @API(since = "5.10", status = EXPERIMENTAL) -fun assertNotNull(actual: T?, message: String): T { +fun assertNotNull(actual: Any?, message: String) { contract { returns() implies (actual != null) } Assertions.assertNotNull(actual, message) - return actual!! } /** @@ -223,7 +221,7 @@ fun assertNotNull(actual: T?, message: String): T { * @see Assertions.assertNotNull */ @API(since = "5.10", status = EXPERIMENTAL) -fun assertNotNull(actual: T?, messageSupplier: () -> String): T { +fun assertNotNull(actual: Any?, messageSupplier: () -> String) { contract { returns() implies (actual != null) @@ -231,7 +229,6 @@ fun assertNotNull(actual: T?, messageSupplier: () -> String): T { } Assertions.assertNotNull(actual, messageSupplier) - return actual!! } /** From e42797849d44a18507faabba80ac7084d7554584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 25 Aug 2023 14:53:03 +0200 Subject: [PATCH 07/17] Increase API version Api version is increased to 5.11 for `assertNull` and `assertNotNull` methods Issue: #1866 --- .../main/kotlin/org/junit/jupiter/api/Assertions.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 980b53bc0382..a492a6027f02 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -113,7 +113,7 @@ fun assertAll(heading: String?, vararg executables: () -> Unit) = * ``` * @see Assertions.assertNull */ -@API(since = "5.10", status = EXPERIMENTAL) +@API(since = "5.11", status = EXPERIMENTAL) fun assertNull(actual: Any?) { contract { returns() implies (actual == null) @@ -134,7 +134,7 @@ fun assertNull(actual: Any?) { * ``` * @see Assertions.assertNull */ -@API(since = "5.10", status = EXPERIMENTAL) +@API(since = "5.11", status = EXPERIMENTAL) fun assertNull(actual: Any?, message: String) { contract { returns() implies (actual == null) @@ -155,7 +155,7 @@ fun assertNull(actual: Any?, message: String) { * ``` * @see Assertions.assertNull */ -@API(since = "5.10", status = EXPERIMENTAL) +@API(since = "5.11", status = EXPERIMENTAL) fun assertNull(actual: Any?, messageSupplier: () -> String) { contract { returns() implies (actual == null) @@ -178,7 +178,7 @@ fun assertNull(actual: Any?, messageSupplier: () -> String) { * ``` * @see Assertions.assertNotNull */ -@API(since = "5.10", status = EXPERIMENTAL) +@API(since = "5.11", status = EXPERIMENTAL) fun assertNotNull(actual: Any?) { contract { returns() implies (actual != null) @@ -199,7 +199,7 @@ fun assertNotNull(actual: Any?) { * ``` * @see Assertions.assertNotNull */ -@API(since = "5.10", status = EXPERIMENTAL) +@API(since = "5.11", status = EXPERIMENTAL) fun assertNotNull(actual: Any?, message: String) { contract { returns() implies (actual != null) @@ -220,7 +220,7 @@ fun assertNotNull(actual: Any?, message: String) { * ``` * @see Assertions.assertNotNull */ -@API(since = "5.10", status = EXPERIMENTAL) +@API(since = "5.11", status = EXPERIMENTAL) fun assertNotNull(actual: Any?, messageSupplier: () -> String) { contract { returns() implies (actual != null) From 544b4e1227bb05ab4fc19724def9aa5461f6a5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 25 Aug 2023 15:07:12 +0200 Subject: [PATCH 08/17] Add assertInstanceOf methods Added `assertInstanceOf` assertions with upcasting contracts Issue: #1866 --- .../org/junit/jupiter/api/Assertions.kt | 65 +++++++++++++++++++ .../jupiter/api/KotlinAssertionsTests.kt | 8 +++ 2 files changed, 73 insertions(+) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index a492a6027f02..0db69152047d 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -231,6 +231,71 @@ fun assertNotNull(actual: Any?, messageSupplier: () -> String) { Assertions.assertNotNull(actual, messageSupplier) } +/** + * Example usage: + * ```kotlin + * val string: Any = ... + * + * assertInstanceOf(string) + * + * // compiler smart casts string to a String object + * assertTrue(string.isNotEmpty()) + * ``` + * @see Assertions.assertInstanceOf + */ +@API(since = "5.11", status = EXPERIMENTAL) +inline fun assertInstanceOf(actual: Any) { + contract { + returns() implies (actual is T) + } + + Assertions.assertInstanceOf(T::class.java, actual) +} + +/** + * Example usage: + * ```kotlin + * val string: Any = ... + * + * assertInstanceOf(string, "Should be a String") + * + * // compiler smart casts string to a String object + * assertTrue(string.isNotEmpty()) + * ``` + * @see Assertions.assertInstanceOf + */ +@API(since = "5.11", status = EXPERIMENTAL) +inline fun assertInstanceOf(actual: Any, message: String) { + contract { + returns() implies (actual is T) + } + + Assertions.assertInstanceOf(T::class.java, actual, message) +} + +/** + * Example usage: + * ```kotlin + * val string: Any = ... + * + * assertInstanceOf(string) { "Should be a String" } + * + * // compiler smart casts string to a String object + * assertTrue(string.isNotEmpty()) + * ``` + * @see Assertions.assertInstanceOf + */ +@API(since = "5.11", status = EXPERIMENTAL) +inline fun assertInstanceOf(actual: Any, noinline messageSupplier: () -> String) { + contract { + returns() implies (actual is T) + + callsInPlace(messageSupplier, AT_MOST_ONCE) + } + + Assertions.assertInstanceOf(T::class.java, actual, messageSupplier) +} + /** * Example usage: * ```kotlin diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index eedcaf6b10bd..b2dc49883015 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -228,6 +228,14 @@ class KotlinAssertionsTests { // nullableString?.isEmpty() } + @Test + fun `assertInstanceOf with compiler smart cast`() { + val string: Any = "string" + + assertInstanceOf(string) + assertFalse(string.isEmpty()) // smart cast to a String object + } + @Test fun `assertDoesNotThrow with value initialization in lambda`() { val value: Int From 437d29e034c039844bde3d9988b346dadc3a13fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 25 Aug 2023 16:31:36 +0200 Subject: [PATCH 09/17] assertInstanceOf methods accept nullable values Issue: #1866 --- .../src/main/kotlin/org/junit/jupiter/api/Assertions.kt | 6 +++--- .../org/junit/jupiter/api/KotlinAssertionsTests.kt | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 0db69152047d..89857de0e9a7 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -244,7 +244,7 @@ fun assertNotNull(actual: Any?, messageSupplier: () -> String) { * @see Assertions.assertInstanceOf */ @API(since = "5.11", status = EXPERIMENTAL) -inline fun assertInstanceOf(actual: Any) { +inline fun assertInstanceOf(actual: Any?) { contract { returns() implies (actual is T) } @@ -265,7 +265,7 @@ inline fun assertInstanceOf(actual: Any) { * @see Assertions.assertInstanceOf */ @API(since = "5.11", status = EXPERIMENTAL) -inline fun assertInstanceOf(actual: Any, message: String) { +inline fun assertInstanceOf(actual: Any?, message: String) { contract { returns() implies (actual is T) } @@ -286,7 +286,7 @@ inline fun assertInstanceOf(actual: Any, message: String) { * @see Assertions.assertInstanceOf */ @API(since = "5.11", status = EXPERIMENTAL) -inline fun assertInstanceOf(actual: Any, noinline messageSupplier: () -> String) { +inline fun assertInstanceOf(actual: Any?, noinline messageSupplier: () -> String) { contract { returns() implies (actual is T) diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index b2dc49883015..b40859d27512 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -236,6 +236,15 @@ class KotlinAssertionsTests { assertFalse(string.isEmpty()) // smart cast to a String object } + @Test + fun `assertInstanceOf with a null value`() { + val error = assertThrows { + assertInstanceOf(null) + } + + assertMessageStartsWith(error, "Unexpected null value") + } + @Test fun `assertDoesNotThrow with value initialization in lambda`() { val value: Int From 43c08ea887e3f597693d048cd6ea71e4971e5daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sun, 27 Aug 2023 16:25:27 +0200 Subject: [PATCH 10/17] Test for assertInstanceOf nullable upcast Issue: #1866 --- .../kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index b40859d27512..db9fa5018412 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -236,6 +236,14 @@ class KotlinAssertionsTests { assertFalse(string.isEmpty()) // smart cast to a String object } + @Test + fun `assertInstanceOf with compiler nullable smart cast`() { + val string: Any? = "string" + + assertInstanceOf(string) + assertFalse(string.isEmpty()) // smart cast to a non-null String object + } + @Test fun `assertInstanceOf with a null value`() { val error = assertThrows { From ff9fd39a345c91f371f1d8635946f4e5aa6ccf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sun, 27 Aug 2023 16:33:56 +0200 Subject: [PATCH 11/17] Specify lambda InvocationKind explicitly for assertThrows methods Issue: #1866 --- .../src/main/kotlin/org/junit/jupiter/api/Assertions.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 89857de0e9a7..bd901c1e7a13 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -21,6 +21,7 @@ import java.util.function.Supplier import java.util.stream.Stream import kotlin.contracts.InvocationKind.AT_MOST_ONCE import kotlin.contracts.InvocationKind.EXACTLY_ONCE +import kotlin.contracts.InvocationKind.UNKNOWN import kotlin.contracts.contract /** @@ -35,7 +36,7 @@ fun fail(message: String?, throwable: Throwable? = null): Nothing = @JvmName("fail_nonNullableLambda") fun fail(message: () -> String): Nothing { contract { - callsInPlace(message) + callsInPlace(message, UNKNOWN) } return Assertions.fail(message) @@ -308,7 +309,7 @@ inline fun assertInstanceOf(actual: Any?, noinline messageSupp */ inline fun assertThrows(executable: () -> Unit): T { contract { - callsInPlace(executable) + callsInPlace(executable, UNKNOWN) } val throwable: Throwable? = try { @@ -336,7 +337,7 @@ inline fun assertThrows(executable: () -> Unit): T { */ inline fun assertThrows(message: String, executable: () -> Unit): T { contract { - callsInPlace(executable) + callsInPlace(executable, UNKNOWN) } return assertThrows({ message }, executable) @@ -354,7 +355,7 @@ inline fun assertThrows(message: String, executable: () */ inline fun assertThrows(noinline message: () -> String, executable: () -> Unit): T { contract { - callsInPlace(executable) + callsInPlace(executable, UNKNOWN) callsInPlace(message, AT_MOST_ONCE) } From 5a70965434d229d774e83b4346df2c293fa996c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 1 Sep 2023 21:47:58 +0200 Subject: [PATCH 12/17] Rename variable names in new assertion examples Issue: #1866 --- .../org/junit/jupiter/api/Assertions.kt | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index bd901c1e7a13..6105579258f6 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -105,12 +105,12 @@ fun assertAll(heading: String?, vararg executables: () -> Unit) = /** * Example usage: * ```kotlin - * val string: String? = ... + * val nullableString: String? = ... * - * assertNull(string) + * assertNull(nullableString) * - * // compiler won't allow even safe calls, since the string is always null - * // string?.isNotEmpty() + * // compiler won't allow even safe calls, since the nullableString is always null + * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull */ @@ -126,12 +126,12 @@ fun assertNull(actual: Any?) { /** * Example usage: * ```kotlin - * val string: String? = ... + * val nullableString: String? = ... * - * assertNull(string, "Should be nullable") + * assertNull(nullableString, "Should be nullable") * - * // compiler won't allow even safe calls, since the string is always null - * // string?.isNotEmpty() + * // compiler won't allow even safe calls, since the nullableString is always null + * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull */ @@ -147,12 +147,12 @@ fun assertNull(actual: Any?, message: String) { /** * Example usage: * ```kotlin - * val string: String? = ... + * val nullableString: String? = ... * - * assertNull(string) { "Should be nullable" } + * assertNull(nullableString) { "Should be nullable" } * - * // compiler won't allow even safe calls, since the string is always null - * // string?.isNotEmpty() + * // compiler won't allow even safe calls, since the nullableString is always null + * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull */ @@ -170,12 +170,12 @@ fun assertNull(actual: Any?, messageSupplier: () -> String) { /** * Example usage: * ```kotlin - * val string: String? = ... + * val nullableString: String? = ... * - * assertNotNull(string) + * assertNotNull(nullableString) * * // compiler smart casts nullableString to a non-nullable object - * assertTrue(string.isNotEmpty()) + * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull */ @@ -191,12 +191,12 @@ fun assertNotNull(actual: Any?) { /** * Example usage: * ```kotlin - * val string: String? = ... + * val nullableString: String? = ... * - * assertNotNull(string, "Should be non-nullable") + * assertNotNull(nullableString, "Should be non-nullable") * * // compiler smart casts nullableString to a non-nullable object - * assertTrue(string.isNotEmpty()) + * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull */ @@ -212,12 +212,12 @@ fun assertNotNull(actual: Any?, message: String) { /** * Example usage: * ```kotlin - * val string: String? = ... + * val nullableString: String? = ... * - * assertNotNull(string) { "Should be non-nullable" } + * assertNotNull(nullableString) { "Should be non-nullable" } * * // compiler smart casts nullableString to a non-nullable object - * assertTrue(string.isNotEmpty()) + * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull */ @@ -235,12 +235,12 @@ fun assertNotNull(actual: Any?, messageSupplier: () -> String) { /** * Example usage: * ```kotlin - * val string: Any = ... + * val maybeString: Any = ... * - * assertInstanceOf(string) + * assertInstanceOf(maybeString) * - * // compiler smart casts string to a String object - * assertTrue(string.isNotEmpty()) + * // compiler smart casts maybeString to a String object + * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf */ @@ -256,12 +256,12 @@ inline fun assertInstanceOf(actual: Any?) { /** * Example usage: * ```kotlin - * val string: Any = ... + * val maybeString: Any = ... * - * assertInstanceOf(string, "Should be a String") + * assertInstanceOf(maybeString, "Should be a String") * - * // compiler smart casts string to a String object - * assertTrue(string.isNotEmpty()) + * // compiler smart casts maybeString to a String object + * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf */ @@ -277,12 +277,12 @@ inline fun assertInstanceOf(actual: Any?, message: String) { /** * Example usage: * ```kotlin - * val string: Any = ... + * val maybeString: Any = ... * - * assertInstanceOf(string) { "Should be a String" } + * assertInstanceOf(maybeString) { "Should be a String" } * - * // compiler smart casts string to a String object - * assertTrue(string.isNotEmpty()) + * // compiler smart casts maybeString to a String object + * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf */ @@ -453,7 +453,7 @@ internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier assertTimeout(timeout: Duration, executable: () -> R): R { @@ -472,7 +472,7 @@ fun assertTimeout(timeout: Duration, executable: () -> R): R { * } * ``` * @see Assertions.assertTimeout - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeout(timeout: Duration, message: String, executable: () -> R): R { @@ -491,7 +491,7 @@ fun assertTimeout(timeout: Duration, message: String, executable: () -> R): * } * ``` * @see Assertions.assertTimeout - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeout(timeout: Duration, message: () -> String, executable: () -> R): R { @@ -511,7 +511,7 @@ fun assertTimeout(timeout: Duration, message: () -> String, executable: () - * } * ``` * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { @@ -530,7 +530,7 @@ fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { * } * ``` * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R { @@ -549,7 +549,7 @@ fun assertTimeoutPreemptively(timeout: Duration, message: String, executable * } * ``` * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R { From 666f73fb755daf5668435a516c7428ed8b12e6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 1 Sep 2023 22:22:11 +0200 Subject: [PATCH 13/17] Add API annotation for new fail assertion Issue: #1866 --- .../src/main/kotlin/org/junit/jupiter/api/Assertions.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 6105579258f6..ebf8a3959055 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -33,13 +33,14 @@ fun fail(message: String?, throwable: Throwable? = null): Nothing = /** * @see Assertions.fail */ +@API(since = "5.11", status = EXPERIMENTAL) @JvmName("fail_nonNullableLambda") fun fail(message: () -> String): Nothing { contract { callsInPlace(message, UNKNOWN) } - return Assertions.fail(message) + return Assertions.fail(message) } /** From 9920cf97ee5d5e57b27349ca9e491f3537040481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 1 Sep 2023 22:30:11 +0200 Subject: [PATCH 14/17] Make the comments be sentences Issue: #1866 --- .../kotlin/org/junit/jupiter/api/Assertions.kt | 18 +++++++++--------- .../junit/jupiter/api/KotlinAssertionsTests.kt | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index ebf8a3959055..a9f2d6b6d7cb 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -110,7 +110,7 @@ fun assertAll(heading: String?, vararg executables: () -> Unit) = * * assertNull(nullableString) * - * // compiler won't allow even safe calls, since the nullableString is always null + * // The compiler won't allow even safe calls, since nullableString is always null. * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull @@ -131,7 +131,7 @@ fun assertNull(actual: Any?) { * * assertNull(nullableString, "Should be nullable") * - * // compiler won't allow even safe calls, since the nullableString is always null + * // The compiler won't allow even safe calls, since nullableString is always null. * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull @@ -152,7 +152,7 @@ fun assertNull(actual: Any?, message: String) { * * assertNull(nullableString) { "Should be nullable" } * - * // compiler won't allow even safe calls, since the nullableString is always null + * // The compiler won't allow even safe calls, since nullableString is always null. * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull @@ -175,7 +175,7 @@ fun assertNull(actual: Any?, messageSupplier: () -> String) { * * assertNotNull(nullableString) * - * // compiler smart casts nullableString to a non-nullable object + * // The compiler smart casts nullableString to a non-nullable object. * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull @@ -196,7 +196,7 @@ fun assertNotNull(actual: Any?) { * * assertNotNull(nullableString, "Should be non-nullable") * - * // compiler smart casts nullableString to a non-nullable object + * // The compiler smart casts nullableString to a non-nullable object. * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull @@ -217,7 +217,7 @@ fun assertNotNull(actual: Any?, message: String) { * * assertNotNull(nullableString) { "Should be non-nullable" } * - * // compiler smart casts nullableString to a non-nullable object + * // The compiler smart casts nullableString to a non-nullable object. * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull @@ -240,7 +240,7 @@ fun assertNotNull(actual: Any?, messageSupplier: () -> String) { * * assertInstanceOf(maybeString) * - * // compiler smart casts maybeString to a String object + * // The compiler smart casts maybeString to a String object. * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf @@ -261,7 +261,7 @@ inline fun assertInstanceOf(actual: Any?) { * * assertInstanceOf(maybeString, "Should be a String") * - * // compiler smart casts maybeString to a String object + * // The compiler smart casts maybeString to a String object. * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf @@ -282,7 +282,7 @@ inline fun assertInstanceOf(actual: Any?, message: String) { * * assertInstanceOf(maybeString) { "Should be a String" } * - * // compiler smart casts maybeString to a String object + * // The compiler smart casts maybeString to a String object. * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index db9fa5018412..d41a2582c363 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -216,7 +216,7 @@ class KotlinAssertionsTests { val nullableString: String? = "string" assertNotNull(nullableString) - assertFalse(nullableString.isEmpty()) // smart cast to a non nullable object + assertFalse(nullableString.isEmpty()) // A smart cast to a non-nullable object. } @Test @@ -224,7 +224,7 @@ class KotlinAssertionsTests { val nullableString: String? = null assertNull(nullableString) - // even safe call is not allowed, because compiler knows that string is always null + // Even safe call is not allowed because compiler knows that nullableString is always null. // nullableString?.isEmpty() } @@ -233,7 +233,7 @@ class KotlinAssertionsTests { val string: Any = "string" assertInstanceOf(string) - assertFalse(string.isEmpty()) // smart cast to a String object + assertFalse(string.isEmpty()) // A smart cast to a String object. } @Test @@ -241,7 +241,7 @@ class KotlinAssertionsTests { val string: Any? = "string" assertInstanceOf(string) - assertFalse(string.isEmpty()) // smart cast to a non-null String object + assertFalse(string.isEmpty()) // A smart cast to a non-nullable String object. } @Test From 4d2148639dcf9a29402a703fff839fd4945440d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 1 Sep 2023 22:37:21 +0200 Subject: [PATCH 15/17] Adjust contract for assertTimeoutPreemptively Invocation kind for the executable is UNKNOWN for assertTimeoutPreemptively methods Issue: #1866 --- .../src/main/kotlin/org/junit/jupiter/api/Assertions.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index a9f2d6b6d7cb..03e40bcba696 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -517,7 +517,7 @@ fun assertTimeout(timeout: Duration, message: () -> String, executable: () - @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { contract { - callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(executable, UNKNOWN) } return Assertions.assertTimeoutPreemptively(timeout, executable) @@ -536,7 +536,7 @@ fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R { contract { - callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(executable, UNKNOWN) } return Assertions.assertTimeoutPreemptively(timeout, executable, message) @@ -555,7 +555,7 @@ fun assertTimeoutPreemptively(timeout: Duration, message: String, executable @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R { contract { - callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(executable, UNKNOWN) callsInPlace(message, AT_MOST_ONCE) } From 30b9b787225ce60c7353739662f26abb2465997d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Fri, 8 Sep 2023 15:37:16 +0200 Subject: [PATCH 16/17] Removed contracts for methods where lambdas may throw exceptions Issue: #1866 --- .../org/junit/jupiter/api/Assertions.kt | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 03e40bcba696..b71e19a8673d 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -21,7 +21,6 @@ import java.util.function.Supplier import java.util.stream.Stream import kotlin.contracts.InvocationKind.AT_MOST_ONCE import kotlin.contracts.InvocationKind.EXACTLY_ONCE -import kotlin.contracts.InvocationKind.UNKNOWN import kotlin.contracts.contract /** @@ -37,7 +36,7 @@ fun fail(message: String?, throwable: Throwable? = null): Nothing = @JvmName("fail_nonNullableLambda") fun fail(message: () -> String): Nothing { contract { - callsInPlace(message, UNKNOWN) + callsInPlace(message, EXACTLY_ONCE) } return Assertions.fail(message) @@ -309,10 +308,6 @@ inline fun assertInstanceOf(actual: Any?, noinline messageSupp * @see Assertions.assertThrows */ inline fun assertThrows(executable: () -> Unit): T { - contract { - callsInPlace(executable, UNKNOWN) - } - val throwable: Throwable? = try { executable() } catch (caught: Throwable) { @@ -336,13 +331,8 @@ inline fun assertThrows(executable: () -> Unit): T { * ``` * @see Assertions.assertThrows */ -inline fun assertThrows(message: String, executable: () -> Unit): T { - contract { - callsInPlace(executable, UNKNOWN) - } - - return assertThrows({ message }, executable) -} +inline fun assertThrows(message: String, executable: () -> Unit): T = + assertThrows({ message }, executable) /** * Example usage: @@ -356,7 +346,6 @@ inline fun assertThrows(message: String, executable: () */ inline fun assertThrows(noinline message: () -> String, executable: () -> Unit): T { contract { - callsInPlace(executable, UNKNOWN) callsInPlace(message, AT_MOST_ONCE) } @@ -515,13 +504,8 @@ fun assertTimeout(timeout: Duration, message: () -> String, executable: () - * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { - contract { - callsInPlace(executable, UNKNOWN) - } - - return Assertions.assertTimeoutPreemptively(timeout, executable) -} +fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R = + Assertions.assertTimeoutPreemptively(timeout, executable) /** * Example usage: @@ -534,13 +518,8 @@ fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R { * @param R the result of the [executable]. */ @API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R { - contract { - callsInPlace(executable, UNKNOWN) - } - - return Assertions.assertTimeoutPreemptively(timeout, executable, message) -} +fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R = + Assertions.assertTimeoutPreemptively(timeout, executable, message) /** * Example usage: @@ -555,7 +534,6 @@ fun assertTimeoutPreemptively(timeout: Duration, message: String, executable @API(status = EXPERIMENTAL, since = "5.5") fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R { contract { - callsInPlace(executable, UNKNOWN) callsInPlace(message, AT_MOST_ONCE) } From 41f00c0dd2536a2e53994dfb7ac9ae13d70a0505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sat, 23 Sep 2023 23:31:17 +0200 Subject: [PATCH 17/17] Add more tests for methods with contracts Issue: #1866 --- .../api/KotlinAssertTimeoutAssertionsTests.kt | 49 +++++++ .../jupiter/api/KotlinAssertionsTests.kt | 125 ++++++++++++++++-- 2 files changed, 163 insertions(+), 11 deletions(-) diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt index 9170c22c9e7d..e0ef5a9096ad 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt @@ -145,6 +145,41 @@ internal class KotlinAssertTimeoutAssertionsTests { assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") } + @Test + fun `assertTimeout with value initialization in lambda`() { + val value: Int + + assertTimeout(ofMillis(500)) { value = 10 } + + assertEquals(10, value) + } + + @Test + fun `assertTimeout with message and value initialization in lambda`() { + val value: Int + + assertTimeout(ofMillis(500), "message") { value = 10 } + + assertEquals(10, value) + } + + @Test + fun `assertTimeout with message supplier and value initialization in lambda`() { + val value: Int + val valueInMessageSupplier: Int + + assertTimeout( + timeout = ofMillis(500), + message = { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + "message" + }, + executable = { value = 10 } + ) + + assertEquals(10, value) + } + // -- executable - preemptively --- @Test @@ -266,6 +301,20 @@ internal class KotlinAssertTimeoutAssertionsTests { assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") } + @Test + fun `assertTimeoutPreemptively with message supplier and value initialization in lambda`() { + val valueInMessageSupplier: Int + + assertTimeoutPreemptively( + timeout = ofMillis(500), + message = { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + "message" + }, + executable = {} + ) + } + /** * Take a nap for 100 milliseconds. */ diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index d41a2582c363..869ab55f322c 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -61,6 +61,17 @@ class KotlinAssertionsTests { assertThrows({ "should fail" }) { fail(null as Throwable?) } } + @Test + fun `assertThrows with message supplier`() { + val valueInMessageSupplier: Int + + assertThrows({ + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "should fail" + }) { fail("message") } + } + @Test fun `expected context exception testing`() = runBlocking { assertThrows("Should fail async") { @@ -184,6 +195,38 @@ class KotlinAssertionsTests { ) ) + @Test + fun `assertDoesNotThrow with value initialization in lambda`() { + val value: Int + + assertDoesNotThrow { value = 10 } + + assertEquals(10, value) + } + + @Test + fun `assertDoesNotThrow with message and value initialization in lambda`() { + val value: Int + + assertDoesNotThrow("message") { value = 10 } + + assertEquals(10, value) + } + + @Test + fun `assertDoesNotThrow with message supplier and value initialization in lambda`() { + val value: Int + val valueInMessageSupplier: Int + + assertDoesNotThrow({ + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "message" + }) { value = 10 } + + assertEquals(10, value) + } + @Test fun `assertAll with stream of functions that throw AssertionErrors`() { val multipleFailuresError = assertThrows("Should have thrown multiple errors") { @@ -219,6 +262,29 @@ class KotlinAssertionsTests { assertFalse(nullableString.isEmpty()) // A smart cast to a non-nullable object. } + @Test + fun `assertNotNull with message and compiler smart cast`() { + val nullableString: String? = "string" + + assertNotNull(nullableString, "nullableString is null") + assertFalse(nullableString.isEmpty()) // A smart cast to a non-nullable object. + } + + @Test + fun `assertNotNull with message supplier and compiler smart cast`() { + val nullableString: String? = "string" + + val valueInMessageSupplier: Int + + assertNotNull(nullableString) { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "nullableString is null" + } + + assertFalse(nullableString.isEmpty()) // A smart cast to a non-nullable object. + } + @Test fun `assertNull with compiler smart cast`() { val nullableString: String? = null @@ -228,20 +294,45 @@ class KotlinAssertionsTests { // nullableString?.isEmpty() } + @Test + fun `assertNull with message and compiler smart cast`() { + val nullableString: String? = null + + assertNull(nullableString, "nullableString is not null") + // Even safe call is not allowed because compiler knows that nullableString is always null. + // nullableString?.isEmpty() + } + + @Test + fun `assertNull with message supplier and compiler smart cast`() { + val nullableString: String? = null + + val valueInMessageSupplier: Int + + assertNull(nullableString) { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "nullableString is not null" + } + + // Even safe call is not allowed because compiler knows that nullableString is always null. + // nullableString?.isEmpty() + } + @Test fun `assertInstanceOf with compiler smart cast`() { - val string: Any = "string" + val maybeString: Any = "string" - assertInstanceOf(string) - assertFalse(string.isEmpty()) // A smart cast to a String object. + assertInstanceOf(maybeString) + assertFalse(maybeString.isEmpty()) // A smart cast to a String object. } @Test fun `assertInstanceOf with compiler nullable smart cast`() { - val string: Any? = "string" + val maybeString: Any? = "string" - assertInstanceOf(string) - assertFalse(string.isEmpty()) // A smart cast to a non-nullable String object. + assertInstanceOf(maybeString) + assertFalse(maybeString.isEmpty()) // A smart cast to a non-nullable String object. } @Test @@ -254,14 +345,26 @@ class KotlinAssertionsTests { } @Test - fun `assertDoesNotThrow with value initialization in lambda`() { - val value: Int + fun `assertInstanceOf with message and compiler smart cast`() { + val maybeString: Any = "string" + + assertInstanceOf(maybeString, "maybeString is not an instance of String") + assertFalse(maybeString.isEmpty()) // A smart cast to a String object. + } + + @Test + fun `assertInstanceOf with message supplier and compiler smart cast`() { + val maybeString: Any = "string" + + val valueInMessageSupplier: Int - assertDoesNotThrow { - value = 10 + assertInstanceOf(maybeString) { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "maybeString is not an instance of String" } - assertEquals(10, value) + assertFalse(maybeString.isEmpty()) // A smart cast to a String object. } companion object {