diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8620c88b46ed..6501dd44dad3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,7 +87,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: - java-version: 21 + java-version: 24 distribution: temurin - uses: sbt/setup-sbt@26ab4b0fa1c47fa62fc1f6e51823a658fb6c760c # v1.1.7 - name: Update JUnit dependencies in examples @@ -245,7 +245,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: - java-version: 21 + java-version: 24 distribution: temurin - uses: sbt/setup-sbt@26ab4b0fa1c47fa62fc1f6e51823a658fb6c760c # v1.1.7 - name: Update JUnit dependencies in examples diff --git a/README.md b/README.md index b6b930b1a4d8..065e72d655a9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This repository is the home of _JUnit 5_. ## Latest Releases -- General Availability (GA): [JUnit 5.13.3](https://github.com/junit-team/junit-framework/releases/tag/r5.13.3) (July 4, 2025) +- General Availability (GA): [JUnit 5.13.4](https://github.com/junit-team/junit-framework/releases/tag/r5.13.4) (July 21, 2025) - Preview (Milestone/Release Candidate): [JUnit 5.13.0-RC1](https://github.com/junit-team/junit-framework/releases/tag/r5.13.0-RC1) (May 16, 2025) ## Documentation diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 8bc95ee43e4f..c5ff8d4ef654 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -17,6 +17,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-5.13.4.adoc[] + include::{basedir}/release-notes-5.13.3.adoc[] include::{basedir}/release-notes-5.13.2.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index 670bf3c42edf..1628c1aded6d 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -10,6 +10,16 @@ link:{junit-framework-repo}+/milestone/100?closed=1+[5.13.3] milestone page in t repository on GitHub. +[[release-notes-5.13.3-overall-improvements]] +=== Overall Changes + +[[release-notes-5.13.3-overall-new-features-and-improvements]] +==== New Features and Improvements + +* All _experimental_ APIs have been promoted to _maintained_ to indicate that they won't + be removed in any future 5.x release. + + [[release-notes-5.13.3-junit-platform]] === JUnit Platform @@ -22,6 +32,8 @@ No changes. [[release-notes-5.13.3-junit-jupiter-bug-fixes]] ==== Bug Fixes +* Fix regression that caused top-level and static member classes annotated with `@Nested` + to no longer be executed because they caused a discovery issue to be reported. * Stop reporting discovery issues for composed annotation classes that are meta-annotated with `@Nested`. * Stop reporting discovery issues for `DefaultImpls` classes generated by the Kotlin diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc new file mode 100644 index 000000000000..d36e84b344e6 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -0,0 +1,48 @@ +[[release-notes-5.13.4]] +== 5.13.4 + +*Date of Release:* July 21, 2025 + +*Scope:* Bug fixes and enhancements since 5.13.3 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit-framework-repo}+/milestone/101?closed=1+[5.13.4] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-5.13.4-overall-improvements]] +=== Overall Changes + +[[release-notes-5.13.4-overall-new-features-and-improvements]] +==== New Features and Improvements + +* Remove `java.*` packages from `Import-Package` headers in all jar manifests to maximize + compatibility with older OSGi runtimes. + + +[[release-notes-5.13.4-junit-platform]] +=== JUnit Platform + +[[release-notes-5.13.4-junit-platform-bug-fixes]] +==== Bug Fixes + +* `ClasspathResourceSelector` no longer allows to be constructed with a resource name that + is blank after removing the leading slash. +* `PackageSource.from(String)` now allows to be constructed with an empty string to + indicate the default package. + + +[[release-notes-5.13.4-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.13.4-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* Log only once per implementation type for `CloseableResource` implementations that do + not implement `AutoCloseable` to avoid flooding console output with this warning. + + +[[release-notes-5.13.4-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/gradle.properties b/gradle.properties index 108a34126273..c01c69f82eb0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.13.3 +version = 5.13.4 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.13.3 +platformVersion = 1.13.4 vintageGroup = org.junit.vintage -vintageVersion = 5.13.3 +vintageVersion = 5.13.4 # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts index d1adaa09ff68..f1007916235f 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts @@ -65,6 +65,11 @@ tasks.withType().named { # Instruct the APIGuardianAnnotations how to operate. # See https://bnd.bndtools.org/instructions/export-apiguardian.html -export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}} + + # Avoid including java packages in Import-Package header to maximize compatibility with older OSGi runtimes. + # See https://bnd.bndtools.org/instructions/noimportjava.html + # Issue: https://github.com/junit-team/junit-framework/issues/4733 + -noimportjava: true """ ) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java index 5aafcc1103e3..035ff228a011 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -53,6 +53,8 @@ abstract class AbstractExtensionContext implements ExtensionContextInternal, AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExtensionContext.class); + private static final Namespace CLOSEABLE_RESOURCE_LOGGING_NAMESPACE = Namespace.create( + AbstractExtensionContext.class, "CloseableResourceLogging"); private final ExtensionContext parent; private final EngineExecutionListener engineExecutionListener; @@ -85,42 +87,39 @@ abstract class AbstractExtensionContext implements Ext .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); // @formatter:on - this.valuesStore = createStore(parent, launcherStoreFacade, createCloseAction()); + this.valuesStore = new NamespacedHierarchicalStore<>(getParentStore(parent), createCloseAction()); + } + + private NamespacedHierarchicalStore getParentStore( + ExtensionContext parent) { + return parent == null // + ? this.launcherStoreFacade.getRequestLevelStore() // + : ((AbstractExtensionContext) parent).valuesStore; } @SuppressWarnings("deprecation") - private NamespacedHierarchicalStore.CloseAction createCloseAction() { + private NamespacedHierarchicalStore.CloseAction createCloseAction() { + Store store = this.launcherStoreFacade.getSessionLevelStore(CLOSEABLE_RESOURCE_LOGGING_NAMESPACE); return (__, ___, value) -> { boolean isAutoCloseEnabled = this.configuration.isClosingStoredAutoCloseablesEnabled(); - if (value instanceof AutoCloseable && isAutoCloseEnabled) { + if (isAutoCloseEnabled && value instanceof AutoCloseable) { ((AutoCloseable) value).close(); return; } if (value instanceof Store.CloseableResource) { if (isAutoCloseEnabled) { - LOGGER.warn( - () -> "Type implements CloseableResource but not AutoCloseable: " + value.getClass().getName()); + store.getOrComputeIfAbsent(value.getClass(), type -> { + LOGGER.warn(() -> "Type implements CloseableResource but not AutoCloseable: " + type.getName()); + return true; + }); } ((Store.CloseableResource) value).close(); } }; } - private static NamespacedHierarchicalStore createStore( - ExtensionContext parent, LauncherStoreFacade launcherStoreFacade, - NamespacedHierarchicalStore.CloseAction closeAction) { - NamespacedHierarchicalStore parentStore; - if (parent == null) { - parentStore = launcherStoreFacade.getRequestLevelStore(); - } - else { - parentStore = ((AbstractExtensionContext) parent).valuesStore; - } - return new NamespacedHierarchicalStore<>(parentStore, closeAction); - } - @Override public void close() { this.valuesStore.close(); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java index 3f7f8d66d395..63a5dcb3834a 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java @@ -24,6 +24,7 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; @@ -61,6 +62,8 @@ public class ClasspathResourceSelector implements DiscoverySelector { ClasspathResourceSelector(String classpathResourceName, FilePosition position) { boolean startsWithSlash = classpathResourceName.startsWith("/"); this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); + Preconditions.notBlank(this.classpathResourceName, + "classpath resource name must not be blank after removing leading slash"); this.position = position; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java index c6bca4e9c0ff..6b9d54b60704 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java @@ -11,6 +11,7 @@ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; import java.util.Objects; @@ -58,7 +59,10 @@ private PackageSource(Package javaPackage) { } private PackageSource(String packageName) { - this.packageName = Preconditions.notBlank(packageName, "package name must not be null or blank"); + Preconditions.notNull(packageName, "package name must not be null"); + Preconditions.condition(packageName.isEmpty() || isNotBlank(packageName), + "package name must not contain only whitespace"); + this.packageName = packageName; } /** diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java index 2b19b1e92f81..ccb7c6f8b76c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.core; +import static org.junit.platform.commons.util.UnrecoverableExceptions.rethrowIfUnrecoverable; import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; @@ -77,7 +78,7 @@ else if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelecto } } - static TestSource toSource(DiscoverySelector selector) { + private static TestSource toSource(DiscoverySelector selector) { if (selector instanceof ClassSelector) { return ClassSource.from(((ClassSelector) selector).getClassName()); } @@ -97,18 +98,27 @@ static TestSource toSource(DiscoverySelector selector) { if (selector instanceof PackageSelector) { return PackageSource.from(((PackageSelector) selector).getPackageName()); } - if (selector instanceof FileSelector) { - FileSelector fileSelector = (FileSelector) selector; - return fileSelector.getPosition() // - .map(DiscoveryIssueCollector::convert) // - .map(position -> FileSource.from(fileSelector.getFile(), position)) // - .orElseGet(() -> FileSource.from(fileSelector.getFile())); - } - if (selector instanceof DirectorySelector) { - return DirectorySource.from(((DirectorySelector) selector).getDirectory()); + try { + // Both FileSource and DirectorySource call File.getCanonicalFile() to normalize the reported file which + // can throw an exception for certain file names on certain file systems. UriSource.from(...) is affected + // as well because it may return a FileSource or DirectorySource + if (selector instanceof FileSelector) { + FileSelector fileSelector = (FileSelector) selector; + return fileSelector.getPosition() // + .map(DiscoveryIssueCollector::convert) // + .map(position -> FileSource.from(fileSelector.getFile(), position)) // + .orElseGet(() -> FileSource.from(fileSelector.getFile())); + } + if (selector instanceof DirectorySelector) { + return DirectorySource.from(((DirectorySelector) selector).getDirectory()); + } + if (selector instanceof UriSelector) { + return UriSource.from(((UriSelector) selector).getUri()); + } } - if (selector instanceof UriSelector) { - return UriSource.from(((UriSelector) selector).getUri()); + catch (Exception ex) { + rethrowIfUnrecoverable(ex); + logger.warn(ex, () -> String.format("Failed to convert DiscoverySelector [%s] into TestSource", selector)); } return null; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java index ce217f3b838f..4cb043fd8ce5 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java @@ -68,19 +68,28 @@ void shouldNotCloseAutoCloseableWhenIsClosingStoredAutoCloseablesEnabledIsFalse( void shouldLogWarningWhenResourceImplementsCloseableResourceButNotAutoCloseableAndConfigIsTrue( @TrackLogRecords LogRecordListener listener) throws Exception { ExecutionRecorder executionRecorder = new ExecutionRecorder(); - CloseableResource resource = new CloseableResource(); - String msg = "Type implements CloseableResource but not AutoCloseable: " + resource.getClass().getName(); + CloseableResource resource1 = new CloseableResource(); + CloseableResource resource2 = new CloseableResource(); + CloseableResource resource3 = new CloseableResource() { + }; when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(true); ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionRecorder, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); - store.put("resource", resource); + store.put("resource1", resource1); + store.put("resource2", resource2); + store.put("resource3", resource3); ((AutoCloseable) extensionContext).close(); - assertThat(listener.stream(Level.WARNING)).map(LogRecord::getMessage).contains(msg); - assertThat(resource.closed).isTrue(); + assertThat(listener.stream(Level.WARNING)).map(LogRecord::getMessage) // + .containsExactlyInAnyOrder( + "Type implements CloseableResource but not AutoCloseable: " + resource1.getClass().getName(), + "Type implements CloseableResource but not AutoCloseable: " + resource3.getClass().getName()); + assertThat(resource1.closed).isTrue(); + assertThat(resource2.closed).isTrue(); + assertThat(resource3.closed).isTrue(); } @Test diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java index c384a90434d2..2e5364143690 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java @@ -292,8 +292,11 @@ void parseDirectorySelectorWithAbsolutePath() { void selectClasspathResourcesPreconditions() { assertViolatesPrecondition(() -> selectClasspathResource((String) null)); assertViolatesPrecondition(() -> selectClasspathResource("")); + assertViolatesPrecondition(() -> selectClasspathResource("/")); assertViolatesPrecondition(() -> selectClasspathResource(" ")); + assertViolatesPrecondition(() -> selectClasspathResource("/ ")); assertViolatesPrecondition(() -> selectClasspathResource("\t")); + assertViolatesPrecondition(() -> selectClasspathResource("/\t")); assertViolatesPrecondition(() -> selectClasspathResource((Set) null)); assertViolatesPrecondition(() -> selectClasspathResource(Collections.emptySet())); assertViolatesPrecondition(() -> selectClasspathResource(Collections.singleton(null))); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java index 8da7f16a8545..7b8c790ae897 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java @@ -18,6 +18,8 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; /** @@ -47,9 +49,11 @@ void packageSourceFromNullPackageReference() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from((Package) null)); } - @Test - void packageSourceFromPackageName() { - var testPackage = getClass().getPackage().getName(); + @ParameterizedTest + @ValueSource(classes = PackageSourceTests.class) + @ValueSource(strings = "DefaultPackageTestCase") + void packageSourceFromPackageName(Class testClass) { + var testPackage = testClass.getPackage().getName(); var source = PackageSource.from(testPackage); assertThat(source.getPackageName()).isEqualTo(testPackage); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java index 40994c481f6e..f230fd1dc037 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java @@ -69,6 +69,7 @@ public static Stream pairs() { new Pair(selectClasspathResource("someResource", FilePosition.from(42, 23)), ClasspathResourceSource.from("someResource", org.junit.platform.engine.support.descriptor.FilePosition.from(42, 23))), // + new Pair(selectPackage(""), PackageSource.from("")), // new Pair(selectPackage("some.package"), PackageSource.from("some.package")), // new Pair(selectFile("someFile"), FileSource.from(new File("someFile"))), // new Pair(selectFile("someFile", FilePosition.from(42)), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy