diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc index 8f7248a50dc0..1e33c99ed364 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc @@ -41,6 +41,8 @@ repository on GitHub. option to change it. To resolve this, a new `commentCharacter` attribute has been added to both annotations. Its default value remains `+++#+++`, but it can now be customized to avoid conflicts with other control characters. +* Fix `IllegalAccessError` thrown when using the Kotlin-specific `assertDoesNotThrow` + assertion. [[release-notes-6.0.1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes 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 eef4af258142..932c281ac982 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 @@ -14,6 +14,7 @@ package org.junit.jupiter.api import org.apiguardian.api.API import org.apiguardian.api.API.Status.MAINTAINED import org.apiguardian.api.API.Status.STABLE +import org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure import org.junit.jupiter.api.function.Executable import org.junit.platform.commons.util.UnrecoverableExceptions.rethrowIfUnrecoverable import java.time.Duration @@ -347,7 +348,6 @@ inline fun assertThrows( * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ -@Suppress("LESS_VISIBLE_TYPE_ACCESS_IN_INLINE_WARNING") @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow(executable: () -> R): R { @@ -359,7 +359,11 @@ inline fun assertDoesNotThrow(executable: () -> R): R { return executable() } catch (t: Throwable) { rethrowIfUnrecoverable(t) - throw AssertDoesNotThrow.createAssertionFailedError(null, t) + val suffix = t.message?.let { if (it.isNotBlank()) ": ${t.message}" else null } ?: "" + throw assertionFailure() + .reason("Unexpected exception thrown: ${t.javaClass.getName()}$suffix") + .cause(t) + .build() } } @@ -396,7 +400,6 @@ inline fun assertDoesNotThrow( * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ -@Suppress("LESS_VISIBLE_TYPE_ACCESS_IN_INLINE_WARNING") @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow( @@ -412,7 +415,12 @@ inline fun assertDoesNotThrow( return executable() } catch (t: Throwable) { rethrowIfUnrecoverable(t) - throw AssertDoesNotThrow.createAssertionFailedError(message(), t) + val suffix = t.message?.let { if (it.isNotBlank()) ": ${t.message}" else null } ?: "" + throw assertionFailure() + .message(message()) + .reason("Unexpected exception thrown: ${t.javaClass.getName()}$suffix") + .cause(t) + .build() } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java similarity index 58% rename from jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java rename to junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java index a5bb7c0886a0..1bbfb6f7d59d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java @@ -10,44 +10,48 @@ package org.junit.jupiter.api; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import java.io.Serializable; +import java.util.List; import java.util.Objects; -import org.apache.groovy.parser.antlr4.util.StringUtils; import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; +import org.opentest4j.MultipleFailuresError; import org.opentest4j.ValueWrapper; -class AssertionTestUtils { +public class AssertionTestUtils { private AssertionTestUtils() { /* no-op */ } - static void expectAssertionFailedError() { + public static void expectAssertionFailedError() { throw new AssertionError("Should have thrown an " + AssertionFailedError.class.getName()); } - static void assertEmptyMessage(Throwable ex) throws AssertionError { - if (!StringUtils.isEmpty(ex.getMessage())) { + public static void assertEmptyMessage(Throwable ex) throws AssertionError { + if (!(ex.getMessage() == null || ex.getMessage().isEmpty())) { throw new AssertionError("Exception message should be empty, but was [" + ex.getMessage() + "]."); } } - static void assertMessageEquals(Throwable ex, String msg) throws AssertionError { + public static void assertMessageEquals(Throwable ex, String msg) throws AssertionError { if (!msg.equals(ex.getMessage())) { throw new AssertionError("Exception message should be [" + msg + "], but was [" + ex.getMessage() + "]."); } } - static void assertMessageMatches(Throwable ex, String regex) throws AssertionError { + public static void assertMessageMatches(Throwable ex, String regex) throws AssertionError { if (ex.getMessage() == null || !ex.getMessage().matches(regex)) { throw new AssertionError("Exception message should match regular expression [" + regex + "], but was [" + ex.getMessage() + "]."); } } - static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws AssertionError { + public static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws AssertionError { if (ex == null) { throw new AssertionError("Cause should not have been null"); } @@ -57,14 +61,14 @@ static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws A } } - static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError { + public static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError { if (ex.getMessage() == null || !ex.getMessage().endsWith(msg)) { throw new AssertionError( "Exception message should end with [" + msg + "], but was [" + ex.getMessage() + "]."); } } - static void assertMessageContains(@Nullable Throwable ex, String msg) throws AssertionError { + public static void assertMessageContains(@Nullable Throwable ex, String msg) throws AssertionError { if (ex == null) { throw new AssertionError("Cause should not have been null"); } @@ -74,7 +78,7 @@ static void assertMessageContains(@Nullable Throwable ex, String msg) throws Ass } } - static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Object expected, + public static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Object expected, @Nullable Object actual) throws AssertionError { if (!wrapsEqualValue(ex.getExpected(), expected)) { throw new AssertionError("Expected value in AssertionFailedError should equal [" @@ -86,7 +90,7 @@ static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Obj } } - static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) { + public static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) { if (value == null || value instanceof Serializable) { return Objects.equals(value, wrapper.getValue()); } @@ -95,14 +99,32 @@ static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) { && Objects.equals(wrapper.getType(), value.getClass()); } - static void recurseIndefinitely() { + public static void recurseIndefinitely() { // simulate infinite recursion throw new StackOverflowError(); } - static void runOutOfMemory() { + public static void runOutOfMemory() { // simulate running out of memory throw new OutOfMemoryError("boom"); } + @SafeVarargs + public static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError, + Class... exceptionTypes) { + + assertNotNull(multipleFailuresError, "MultipleFailuresError"); + List failures = multipleFailuresError.getFailures(); + assertEquals(exceptionTypes.length, failures.size(), "number of failures"); + + // Verify that exceptions are also present as suppressed exceptions. + // https://github.com/junit-team/junit-framework/issues/1602 + Throwable[] suppressed = multipleFailuresError.getSuppressed(); + assertEquals(exceptionTypes.length, suppressed.length, "number of suppressed exceptions"); + + for (int i = 0; i < exceptionTypes.length; i++) { + assertEquals(exceptionTypes[i], failures.get(i).getClass(), "exception type [" + i + "]"); + assertEquals(exceptionTypes[i], suppressed[i].getClass(), "suppressed exception type [" + i + "]"); + } + } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java index 0c11dd9083c4..bce9f639e92e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java @@ -12,10 +12,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedExceptionTypes; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; @@ -23,7 +23,6 @@ import java.io.IOException; import java.util.Collection; -import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.function.Executable; @@ -198,25 +197,6 @@ void assertAllWithParallelStream() { assertThat(multipleFailuresError.getFailures()).hasSize(100).doesNotContainNull(); } - @SafeVarargs - static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError, - Class... exceptionTypes) { - - assertNotNull(multipleFailuresError, "MultipleFailuresError"); - List failures = multipleFailuresError.getFailures(); - assertEquals(exceptionTypes.length, failures.size(), "number of failures"); - - // Verify that exceptions are also present as suppressed exceptions. - // https://github.com/junit-team/junit-framework/issues/1602 - Throwable[] suppressed = multipleFailuresError.getSuppressed(); - assertEquals(exceptionTypes.length, suppressed.length, "number of suppressed exceptions"); - - for (int i = 0; i < exceptionTypes.length; i++) { - assertEquals(exceptionTypes[i], failures.get(i).getClass(), "exception type [" + i + "]"); - assertEquals(exceptionTypes[i], suppressed[i].getClass(), "suppressed exception type [" + i + "]"); - } - } - @SuppressWarnings("serial") private static class EnigmaThrowable extends Throwable { } diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertTimeoutAssertionsTests.kt similarity index 97% rename from jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertTimeoutAssertionsTests.kt index 54b01a0ec3a4..426e3f20546e 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertTimeoutAssertionsTests.kt @@ -7,13 +7,18 @@ * * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.api +package org.junit.jupiter.api.kotlin import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals import org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.assertTimeout +import org.junit.jupiter.api.assertTimeoutPreemptively +import org.junit.jupiter.api.fail import org.junit.platform.commons.util.ExceptionUtils import org.opentest4j.AssertionFailedError import java.time.Duration.ofMillis @@ -21,7 +26,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicBoolean /** - * Unit tests for JUnit Jupiter [Assertions]. + * Unit tests for Kotlin-specific `assertTimeout*` assertions. * * @since 5.5 */ diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertionsTests.kt similarity index 95% rename from jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertionsTests.kt index 41feab4cdcd1..75495f268da0 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertionsTests.kt @@ -7,9 +7,10 @@ * * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.api +package org.junit.jupiter.api.kotlin import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AssertionTestUtils import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals import org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith import org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError @@ -17,7 +18,16 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DynamicContainer.dynamicContainer +import org.junit.jupiter.api.DynamicNode import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestFactory +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertInstanceOf +import org.junit.jupiter.api.assertNull +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.fail import org.opentest4j.AssertionFailedError import org.opentest4j.MultipleFailuresError import java.util.stream.Stream @@ -70,7 +80,7 @@ class KotlinAssertionsTests { } @TestFactory - fun assertDoesNotThrow(): Stream = + fun `assertDoesNotThrow behaves as expected`(): Stream = Stream.of( dynamicContainer( "succeeds when no exception thrown", @@ -223,7 +233,7 @@ class KotlinAssertionsTests { } @Test - fun assertInstanceOf() { + fun `assertInstanceOf succeeds`() { assertInstanceOf(listOf("whatever")) assertInstanceOf(listOf("whatever"), "No random access") assertInstanceOf(listOf("whatever")) { "No random access" } @@ -336,7 +346,7 @@ class KotlinAssertionsTests { fun assertExpectedExceptionTypes( multipleFailuresError: MultipleFailuresError, vararg exceptionTypes: KClass - ) = AssertAllAssertionsTests.assertExpectedExceptionTypes( + ) = AssertionTestUtils.assertExpectedExceptionTypes( multipleFailuresError, *exceptionTypes.map { it.java }.toTypedArray() ) diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinDynamicTests.kt similarity index 88% rename from jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinDynamicTests.kt index ba0dec561903..31627a318a9f 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinDynamicTests.kt @@ -7,10 +7,13 @@ * * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.api +package org.junit.jupiter.api.kotlin import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.TestFactory import java.math.BigDecimal import java.math.BigDecimal.ONE import java.math.MathContext @@ -18,7 +21,7 @@ import java.math.BigInteger as BigInt import java.math.RoundingMode as Rounding /** - * Unit tests for JUnit Jupiter [TestFactory] use in kotlin classes. + * Unit tests for JUnit Jupiter [org.junit.jupiter.api.TestFactory] use in kotlin classes. * * @since 5.12 */ diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinFailAssertionsTests.kt similarity index 95% rename from jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinFailAssertionsTests.kt index c5da45e59173..3c0a5582db79 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinFailAssertionsTests.kt @@ -7,12 +7,15 @@ * * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.api +package org.junit.jupiter.api.kotlin -import org.junit.jupiter.api.AssertEquals.assertEquals import org.junit.jupiter.api.AssertionTestUtils.assertEmptyMessage import org.junit.jupiter.api.AssertionTestUtils.assertMessageContains import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.fail import org.opentest4j.AssertionFailedError import java.util.stream.Stream diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinSuspendFunctionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinSuspendFunctionsTests.kt similarity index 94% rename from jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinSuspendFunctionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinSuspendFunctionsTests.kt index c5b0c742c186..9c784f9c1270 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinSuspendFunctionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinSuspendFunctionsTests.kt @@ -7,12 +7,21 @@ * * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.api +package org.junit.jupiter.api.kotlin import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestFactory +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import org.junit.jupiter.api.TestReporter import org.junit.jupiter.engine.AbstractJupiterTestEngineTests import org.junit.jupiter.params.AfterParameterizedClassInvocation import org.junit.jupiter.params.BeforeParameterizedClassInvocation